При работе с типовыми конфигурациями 1С:Розница и 1С:Бухгалтерия 3.0 многие пользователи сталкиваются с проблемой при обмене данными по возвратам товаров. Если возврат от покупателя происходит не день-в-день, то в документе "Отчет о розничных продажах" (ОРП) в 1С:Бухгалтерии 3.0 (БП3) на закладке "Возвраты" колонка "Дата реализации" остается незаполненной. Отсутствие этой критически важной даты приводит к некорректному расчету себестоимости возвращенных товаров и может вызвать трудности с проведением документа, а также ошибки в бухгалтерском и налоговом учете НДС.
В данной статье мы подробно разберем, почему возникает эта проблема, как она влияет на учет, и какие подходы существуют для ее решения, включая анализ типовых механизмов 1С и разработку собственных обработок.
Для начала давайте выясним, для чего вообще нужна "Дата реализации" при оформлении возвратов не день-в-день. Этот реквизит является ключевым для корректного отражения возврата в бухгалтерском и налоговом учете:
Представим ситуацию: вы продали 10 штук товара по одной себестоимости 1 января, а затем еще 5 штук этого же товара по другой себестоимости 1 февраля. Если покупатель возвращает 7 штук в марте, то для корректного учета важно знать, какие именно 7 штук возвращены (из первой или второй партии, или их комбинация), чтобы сторнировать правильную себестоимость.
Первоначально возникает логичное предположение: если функционал возвратов не день-в-день предусмотрен, то и "Дата реализации" должна передаваться типовым обменом. Однако на практике это часто не так.
Разберем по шагам, что выяснилось в ходе анализа:
СинхронизацияДанныхЧерезУниверсальныйФормат. И именно в этом универсальном формате обмена необходимая логика по передаче "Даты реализации" для возвратов не день-в-день типовыми средствами 1С не реализована или реализована не полностью. Поэтому колонка в БП3 остается пустой.Приведем пример того, как могла бы выглядеть выборка данных в Рознице, если бы она полностью передавалась в БП3 для возвратов (фрагмент кода из типовых правил, который не попадает в ED для данного сценария):
|ВЫБРАТЬ
| ЧекККМТовары.Номенклатура КАК Номенклатура,
| ЧекККМТовары.Характеристика КАК Характеристика,
| СУММА(ЧекККМТовары.Количество) КАК Количество,
| ЧекККМТовары.Цена КАК Цена,
| СУММА(ЧекККМТовары.Сумма) КАК Сумма,
| ЧекККМТовары.СтавкаНДС КАК СтавкаНДС,
| СУММА(ЧекККМТовары.СуммаНДС) КАК СуммаНДС,
| ЧекККМ.ДатаПродажи КАК ДатаРеализации,
| ЧекККМ.НомерЧекаККМ КАК НомерФискальногоЧека,
| ВЫБОР
| КОГДА &ПередаватьЦеныХарактеристик
| ТОГДА ВЫБОР
| КОГДА ЧекККМТовары.Характеристика = ЗНАЧЕНИЕ(Справочник.ХарактеристикиНоменклатуры.ПустаяСсылка)
| ТОГДА ЛОЖЬ
| ИНАЧЕ ИСТИНА
| КОНЕЦ
| ИНАЧЕ ЛОЖЬ
| КОНЕЦ КАК ПередатьИсходящиеДанные,
| ВЫБОР
| КОГДА СпрНоменклатура.СтранаПроисхождения = ЗНАЧЕНИЕ(Справочник.СтраныМира.ПустаяСсылка)
| ТОГДА ЗНАЧЕНИЕ(Справочник.СтраныМира.РОССИЯ)
| ИНАЧЕ СпрНоменклатура.СтранаПроисхождения
| КОНЕЦ КАК СтранаПроисхождения
|ИЗ
| Документ.ЧекККМ.Товары КАК ЧекККМТовары
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ (ВЫБРАТЬ РАЗЛИЧНЫЕ
| Док.Ссылка КАК Ссылка,
| Док.ВидОперации КАК ВидОперации,
| Док.ДатаПродажи,
| Док.НомерЧекаККМ
| ИЗ
| ТаблицаПромежуточная КАК Док
| ГДЕ
| Док.ЭтоВозвратПослеЗакрытияСмены) КАК ЧекККМ
| ПО ЧекККМТовары.Ссылка = ЧекККМ.Ссылка
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК СпрНоменклатура
| ПО (СпрНоменклатура.Ссылка = ЧекККМТовары.Номенклатура)
|ГДЕ
| ЧекККМТовары.Ссылка.ОтчетОРозничныхПродажах = &Ссылка
| И СпрНоменклатура.ТипНоменклатуры = ЗНАЧЕНИЕ(Перечисление.ТипыНоменклатуры.Товар)
|
|СГРУППИРОВАТЬ ПО
| ЧекККМТовары.Номенклатура,
| ЧекККМТовары.Характеристика,
| ЧекККМТовары.Цена,
| ЧекККМТовары.СтавкаНДС,
| ЧекККМ.ДатаПродажи,
| ЧекККМ.НомерЧекаККМ,
| ВЫБОР
| КОГДА СпрНоменклатура.СтранаПроисхождения = ЗНАЧЕНИЕ(Справочник.СтраныМира.ПустаяСсылка)
| ТОГДА ЗНАЧЕНИЕ(Справочник.СтраныМира.РОССИЯ)
| ИНАЧЕ СпрНоменклатура.СтранаПроисхождения
| КОНЕЦ
|;
Данный код ясно демонстрирует намерение 1С связывать возвращаемые товары с их "ДатойПродажи". Однако, поскольку он не срабатывает через используемый механизм обмена, мы вынуждены искать обходные пути.
Для решения описанной проблемы мы можем рассмотреть несколько подходов, каждый из которых имеет свои преимущества и недостатки.
Самый "правильный" и системный подход — это доработка правил обмена, использующих формат EnterpriseData. Этот метод потребует квалифицированного программиста, разбирающегося в механизмах 1С:Конвертация Данных 3.0 (КД3) и структуре EnterpriseData. При реализации подобных доработок будет полезен инструмент для отладки обмена через универсальный формат и внешних обработок.
ЧекККМ.ДатаПродажи.Этот подход обеспечивает наиболее интегрированное и типовое решение, но требует значительных усилий по разработке и поддержке правил обмена при обновлениях конфигураций.
Этот подход является наиболее реалистичным "костылем", но требует глубокого понимания внутренних механизмов 1С:Бухгалтерии 3.0, особенно модуля УчетТоваров, который отвечает за контроль и расчет себестоимости. Наша задача — не просто подставить любую дату, а такую, которая позволит документу корректно провестись, и себестоимость будет рассчитана верно.
Разберем по шагам логику, которую должна выполнять такая обработка:
Для каждой строки возврата в ОРП необходимо найти подходящие документы реализации. Мы будем осуществлять поиск продаж по соответствующей номенклатуре, характеристике, организации и, что важно, складу.
Рассмотрим пример, который поможет нам понять логику подбора:
В этом случае обработка должна будет определить, что 16 штук возвращаются из партии, проданной 1 февраля. "Дата реализации" для этой строки ОРП будет установлена как "1 февраля".
Однако ситуация может быть сложнее:
Здесь нам уже не хватает остатка из продажи от 1 февраля (осталось 20 - 16 = 4 штуки). Следовательно, эти 15 штук нужно "разбить":
В результате, в документе ОРП в БП3 для этого возврата 12 февраля вместо одной строки может появиться две или более, каждая со своей "Датой реализации". Это необходимо для того, чтобы пройти внутренние проверки 1С.
1С:Бухгалтерия 3.0 имеет очень строгие механизмы контроля при проведении документов, связанных с учетом товаров, особенно при возвратах. Наша обработка должна быть спроектирована таким образом, чтобы успешно проходить эти контроли.
Ключевая функция, задействованная в этом процессе, находится в общем модуле УчетТоваров и называется ПодготовитьТаблицуВозвращенныеСписанныеТовары. Проанализируем ее основные моменты:
Функция ПодготовитьТаблицуВозвращенныеСписанныеТовары(ТаблицаТовары, ТаблицаСчетаУчетаТоваров, ТаблицаСписокНоменклатуры, ТаблицаРеквизиты, Отказ) Экспорт
Если Не ЗначениеЗаполнено(ТаблицаТовары)
Или Не ЗначениеЗаполнено(ТаблицаРеквизиты) Тогда
Возврат Неопределено;
КонецЕсли;
ТаблицаРезультата = ПолучитьПустуюТаблицуСписанныеТовары();
Реквизиты = ТаблицаРеквизиты[0];
Если Не Реквизиты.УказанДокументОтгрузки
Или Не ЗначениеЗаполнено(ТаблицаСчетаУчетаТоваров) Тогда
ОбщегоНазначенияБПВызовСервера.ЗагрузитьВТаблицуЗначений(ТаблицаТовары, ТаблицаРезультата);
Возврат ТаблицаРезультата;
КонецЕсли;
// ... Пропуск части кода, связанного с определением счетов учета ...
ДатаДокументаРеализации = Реквизиты.ДатаДокументаРеализации;
Если НачалоДня(Реквизиты.Период) < НачалоДня(ДатаДокументаРеализации) Тогда
ТекстСообщения = НСтр("ru = 'Возврат по документу ""%1"" не может быть раньше %2 г.'");
// ... Формирование сообщения об ошибке ...
Отказ = Истина;
ОбщегоНазначенияБПВызовСервера.ЗагрузитьВТаблицуЗначений(ТаблицаТовары, ТаблицаРезультата);
Возврат ТаблицаРезультата;
Иначе
ВозвратПрошлогоМесяца = НачалоМесяца(Реквизиты.Период) <> НачалоМесяца(ДатаДокументаРеализации);
КонецЕсли;
// ... Пропуск части кода, связанного со складами и подразделениями ...
Если ВозвратПрошлогоМесяца Тогда
ТекстЗапроса = ТекстЗапросаВозвращенныеСписанныеТоварыПрошлогоМесяца(...);
Иначе
ТекстЗапроса = ТекстЗапросаВозвращенныеСписанныеТоварыТекущегоМесяца(...);
КонецЕсли;
ТекстЗапроса = ТекстЗапроса +
"ВЫБРАТЬ
| ВТ_СписанныеТовары.Номенклатура,
| ВТ_СписанныеТовары.Партия,
| ВТ_СписанныеТовары.СчетУчета,
| ЕСТЬNULL(РеквизитыДокументаПартии.ДатаРегистратора, ДАТАВРЕМЯ(1, 1, 1)) КАК ДатаПартии,
| СУММА(ВТ_СписанныеТовары.Сумма) КАК Сумма,
| // ... другие суммы ...
| СУММА(ВТ_СписанныеТовары.Количество) КАК Количество,
| СУММА(ВТ_СписанныеТовары.КоличествоОтгружено) КАК КоличествоОтгружено // Остаток (количество) который можно вернуть по конкретной реализации
|ИЗ
| ВТ_СписанныеТовары КАК ВТ_СписанныеТовары
| // ... соединения ...
|ГДЕ
| ВТ_СписанныеТовары.Номенклатура В (&Товары)
|СГРУППИРОВАТЬ ПО
| // ... поля группировки ...
|ИМЕЮЩИЕ
| СУММА(ВТ_СписанныеТовары.Количество) > 0
|УПОРЯДОЧИТЬ ПО
| ВТ_СписанныеТовары.Номенклатура,
| ДатаПартии УБЫВ";
// ... Установка параметров запроса ...
ТаблицаОтгрузок = Запрос.Выполнить().Выгрузить();
Для каждого СтрокаВозврата Из Параметры.ТаблицаТовары Цикл
КоличествоНеПодобраноПартии = СтрокаВозврата.Количество;
ОтгруженныеПартии = ТаблицаОтгрузок.НайтиСтроки(Новый Структура("Номенклатура, СчетУчета", СтрокаВозврата.Номенклатура, СтрокаВозврата.СчетУчета));
Для каждого СтрокаОтгрузки Из ОтгруженныеПартии Цикл
Если СтрокаОтгрузки.КоличествоОтгружено = 0 Тогда
Продолжить;
КонецЕсли;
НоваяСтрока = ТаблицаРезультата.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаВозврата);
НоваяСтрока.Партия = СтрокаОтгрузки.Партия;
НоваяСтрока.Сделка = Реквизиты.Сделка;
КоличествоВозвращено = Мин(КоличествоНеПодобраноПартии, СтрокаОтгрузки.КоличествоОтгружено);
Если КоличествоВозвращено >= СтрокаОтгрузки.Количество Тогда
// Полный возврат всей партии
НоваяСтрока.Количество = СтрокаОтгрузки.Количество;
НоваяСтрока.СуммаСписания = СтрокаОтгрузки.Сумма;
// ... другие суммы ...
Иначе
// Частичный возврат партии
НоваяСтрока.Количество = КоличествоВозвращено;
Коэфф = КоличествоВозвращено / СтрокаОтгрузки.Количество;
НоваяСтрова.СуммаСписания = Окр(СтрокаОтгрузки.Сумма * Коэфф, 2);
// ... другие суммы ...
КонецЕсли;
КоличествоНеПодобраноПартии = КоличествоНеПодобраноПартии - НоваяСтрока.Количество;
СтрокаОтгрузки.Количество = СтрокаОтгрузки.Количество - НоваяСтрока.Количество;
СтрокаОтгрузки.КоличествоОтгружено = СтрокаОтгрузки.КоличествоОтгружено - НоваяСтрока.Количество;
// ... обновление сумм ...
Если КоличествоНеПодобраноПартии = 0 Тогда
Прервать;
КонецЕсли;
КонецЦикла;
Если КоличествоНеПодобраноПартии > 0 Тогда
ТекстСообщения = НСтр("ru = 'Документом ""%1"" было реализовано ""%2"" на ""%3"" ед. меньше, чем возвращается.'");
// ... Формирование сообщения об ошибке ...
Отказ = Истина;
КонецЕсли;
КонецЦикла;
Возврат ТаблицаРезультата;
КонецФункции
Из анализа этого кода мы видим следующие важные контроли и логику:
Реквизиты.Период) не была раньше "Даты реализации" (ДатаДокументаРеализации). Если это условие нарушается, документ не проводится.КоличествоОтгружено), которое еще может быть возвращено по конкретной реализации (партии). Это означает, что если по одной реализации было продано 20 штук, а уже 16 из них вернули, то максимально можно вернуть еще только 4 штуки из этой же реализации.ОтгруженныеПартии пытается распределить возвращаемое количество по имеющимся партиям (реализациям) в порядке убывания даты (ДатаПартии УБЫВ). Если количество возврата больше, чем остаток по текущей партии, остаток списывается полностью, и поиск продолжается по следующей партии.КоличествоНеПодобраноПартии > 0), выдается сообщение об ошибке, что реализовано меньше, чем возвращается.Таким образом, наша обработка должна не просто "найти дату", а реализовать сложную логику распределения возвращаемого количества по доступным продажам, разбивая документ ОРП на несколько строк с разными датами реализации, чтобы строго соответствовать требованиям 1С.
Еще одно важное открытие, сделанное при практической реализации, касается учета складов. Система 1С, особенно при ведении суммового учета по складам, строго контролирует соответствие склада в документе возврата складу, указанному в документе первоначальной реализации.
В коде это видно по условию ИспользоватьОтборПоСкладуОтгрузки = ВедетсяСуммовойУчетПоСкладам И ЕстьСкладОтгрузки. Это означает, что если в ОРП возврата указан Склад А, а товар был продан со Склада Б, то документ не проведется, даже если на Складе А и Складе Б достаточное количество товара и есть подходящие даты реализации. Такой подход 1С, хотя и обеспечивает строгость учета, может создавать проблемы для клиентоориентированных компаний, которые могут принимать возвраты на любой удобный для клиента склад.
Нашей обработке необходимо учитывать этот аспект и при поиске документов реализации фильтровать их не только по номенклатуре и дате, но и по складу.
Третий подход предполагает изменение самого бизнес-процесса оформления возвратов в 1С:Рознице. Вместо того чтобы формировать "Отчет о розничных продажах" с вкладкой "Возвраты" для не-день-в-день возвратов, можно использовать документ "Возврат товаров от клиента".
Хотя на первый взгляд этот метод кажется простым, его внедрение сопряжено с серьезными организационными и техническими изменениями, которые пользователи форума часто отвергают из-за их масштаба ради "вшивой себестоимости".
Проблема некорректного заполнения "Даты реализации" в ОРП (возврат) при обмене из 1С:Розницы в 1С:Бухгалтерию 3.0 является достаточно распространенной и не имеет простого типового решения "из коробки" в текущих механизмах обмена EnterpriseData. Мы рассмотрели три основных подхода к ее решению:
Наиболее прагматичным и часто выбираемым путем является разработка внешней обработки, которая умеет искать подходящие даты реализации, распределять возвращаемое количество по соответствующим продажам (партиям) и, что критически важно, проходит все строгие контроли 1С:Бухгалтерии 3.0, включая проверку склада отгрузки. Это требует глубокого анализа конфигураций и детальной проработки логики.
При выборе решения всегда учитывайте масштабы вашего бизнеса, объем возвратов и квалификацию ваших специалистов 1С. Важно, чтобы любое внедренное решение было стабильным, поддерживаемым и не создавало новых проблем при обновлениях типовых конфигураций.