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