При работе с торговыми конфигурациями на платформе 1С:Предприятие 8.3 (такими как УТ 11, ERP 2 или Комплексная автоматизация) программисты часто анализируют остатки и доступность товаров с ценой и суммой и сталкиваются со специфическим поведением регистра накопления ТоварыКОтгрузке. Типичная ситуация: при анализе движений через виртуальную таблицу ОстаткиИОбороты мы видим, что документ РеализацияТоваровУслуг (РТУ) формирует одновременно и Приход, и Расход на одну и ту же сумму. Рассмотрим, почему это происходит, как работают границы периодов и как правильно составить запрос для получения остатков.
Для начала проанализируем, какую задачу решает данный регистр. Он предназначен для контроля товаров, которые уже «обещаны» клиенту (зарезервированы или подготовлены к выдаче), но еще не списаны со склада фактически. Логика регистра строится на разделении распоряжения и исполнения. Для оперативного управления такими позициями в документах подойдёт обработка для изменения резерва и отгрузки в заказах.
Рассмотрим два основных сценария работы системы:
ЗаказКлиента выступает распоряжением. При его проведении в регистр записывается Приход (возникает потребность отгрузить). Когда бухгалтер или менеджер создает документ РеализацияТоваровУслуг, он выступает исполнителем распоряжения и делает Расход по этому же регистру, закрывая потребность (иногда требуется запрет превышения количества в ордерах относительно распоряжений).РеализацияТоваровУслуг совмещает в себе обе функции. Он одновременно является и распоряжением, и фактом отгрузки. Чтобы не нарушать целостность отчетов, система делает две записи: сначала Приход (сама реализация фиксирует план), а затем мгновенно Расход (сама же его исполняет). Именно поэтому в запросе мы видим одинаковые цифры в колонках КОтгрузкеПриход и КОтгрузкеРасход.Выясним причину, по которой использование измерения Регистратор в параметрах виртуальной таблицы ОстаткиИОбороты может привести к путанице. Посмотрим на стандартный запрос, который часто пишут новички (аналогичные сложности возникают, когда нужно сравнение оборотов товаров на складах между БП и УТ):
ВЫБРАТЬ
ТоварыКОтгрузке.Регистратор КАК Регистратор,
ТоварыКОтгрузке.Номенклатура КАК Номенклатура,
ТоварыКОтгрузке.КОтгрузкеПриход КАК КОтгрузкеПриход,
ТоварыКОтгрузке.КОтгрузкеРасход КАК КОтгрузкеРасход,
ТоварыКОтгрузке.КОтгрузкеКонечныйОстаток КАК КонечныйОстаток
ИЗ
РегистрНакопления.ТоварыКОтгрузке.ОстаткиИОбороты(&НачалоПериода, &КонецПериода, Регистратор, , ) КАК ТоварыКОтгрузке;
Проанализируем ситуацию: если мы выбираем Регистратор, то КонечныйОстаток будет рассчитан на момент конкретного документа. Если за один день было несколько движений по одной номенклатуре, то итог по группировке «Номенклатура» простым суммированием конечных остатков даст неверный результат. Кроме того, использование Регистратора значительно замедляет работу запроса в крупных базах, так как системе приходится вычислять итоги на каждую точку времени, где есть запись.
Часто возникает вопрос: почему виртуальная таблица Остатки на определенную дату показывает одни данные, а ОстаткиИОбороты за период, заканчивающийся этой же датой — другие? Разберем этот нюанс, который часто всплывает при сравнении остатков и цен товаров между базами:
Остатки по умолчанию возвращает данные на начало секунды указанной даты. Если передать 03.09.2024 00:00:00, то движения самого этого дня в остаток не попадут.ОстаткиИОбороты при указании периода «По» включает движения включительно до конца указанной секунды.Для корректного получения остатков на конец периода в запросах рекомендуется использовать объект Граница в коде 1С или явно приводить дату к концу дня:
// В коде 1С:
Запрос.УстановитьПараметр("КонецПериода", Новый Граница(КонецДня(ВыбДата), ВидГраницы.Включая));
Рассмотрим наиболее эффективный способ решения задачи. Если нам нужно получить список номенклатуры, по которой были движения (приход в резерв), и при этом увидеть актуальный остаток на конец периода, лучше всего использовать Левое Соединение двух виртуальных таблиц. Посмотрим на пример реализации такого запроса:
ВЫБРАТЬ
ТаблицаОборотов.Номенклатура КАК Номенклатура,
ЕСТЬNULL(ТаблицаОстатков.КОтгрузкеОстаток, 0) КАК ОстатокНаКонец
ИЗ
(ВЫБРАТЬ
ТоварыКОтгрузке.Номенклатура КАК Номенклатура
ИЗ
РегистрНакопления.ТоварыКОтгрузке.ОстаткиИОбороты(&НачалоПериода, &КонецПериода, , , ) КАК ТоварыКОтгрузке
ГДЕ
ТоварыКОтгрузке.КОтгрузкеПриход > 0
СГРУППИРОВАТЬ ПО
ТоварыКОтгрузке.Номенклатура) КАК ТаблицаОборотов
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыКОтгрузке.Остатки(&КонецПериода, ) КАК ТаблицаОстатков
ПО ТаблицаОборотов.Номенклатура = ТаблицаОстатков.Номенклатура
В данном примере мы сначала получаем список номенклатуры, по которой были целевые движения (вложенный запрос к ОстаткиИОбороты без детализации до регистратора), а затем присоединяем к ним срез остатков из таблицы Остатки. Это работает быстрее и выдает корректный финальный итог.
Проанализируем дополнительные измерения регистра. В ТоварыКОтгрузке есть измерение ДокументОтгрузки. Посмотрим на разницу:
ЗаказКлиента. Если без заказа — сама РТУ.Если вы хотите увидеть «чистую» картину того, что осталось отгрузить, всегда группируйте данные по измерению ДокументОтгрузки. Это позволит схлопнуть обороты Прихода и Расхода, которые возникают при оформлении реализации без заказа, и получить реальную картину складских потребностей. Для получения наглядных данных без составления запросов вручную подойдёт отчёт для анализа остатков, резервов и товаров к отгрузке.
Подведем итог: понимание того, что РеализацияТоваровУслуг может выступать одновременно в двух ролях, позволяет правильно интерпретировать «странные» обороты в регистрах. А использование соединения виртуальных таблиц вместо детализации до регистратора — залог быстродействия ваших отчетов.