Как избежать зависания себестоимости при нулевом количестве товара в 1С?

Программист 1С v8.3 (Управляемые формы) Управленческий учет IT и автоматизация бизнеса
← На главную

При реализации собственного механизма списания запасов по средней стоимости разработчики часто сталкиваются с классической ошибкой: количественный остаток на складе обнуляется, а на остатке регистра накопления зависает копеечная сумма (например, 0,01 руб.). Проанализируем, почему возникает такая ситуация, как платформа 1С позиционирует документы на временной оси и как правильно организовать алгоритм списания себестоимости.

Выясним причины возникновения проблемы

Появление нулевого остатка с ненулевой стоимостью обычно вызвано сочетанием двух факторов: методической ошибкой при округлении сумм и некорректным получением остатков на временной оси.

Рассмотрим типовую ситуацию: на складе числится 3 единицы товара общей себестоимостью 15,91 руб. Мы оформляем три отдельных расходных документа по 1 штуке. Если мы просто рассчитываем списание по стандартной формуле пропорции с округлением до двух знаков:


СебестоимостьСписания = Окр(КоличествоСписания * СуммаОстаток / КоличествоОстаток, 2);

Для первой штуки получаем: 15,91 / 3 = 5,3033..., после округления — 5,30 руб. Если при проведении второго и третьего документов система видит первоначальный остаток (3 штуки и 15,91 руб.), то каждый из трех документов спишет по 5,30 руб. В сумме будет списано: 5,30 * 3 = 15,90 руб. В результате количество товара станет равным 0, а сумма остатка составит 0,01 руб.

Почему же последующие документы видят первоначальный остаток, если они проводятся последовательно? Причина кроется в передаче обычной даты вместо объекта МоментВремени.

Различие между датой и моментом времени в платформе 1С

Разберем фундаментальное отличие между значением типа Дата и объектом МоментВремени. Дата хранит значение с точностью до секунды. Если несколько документов проведены в пределах одной и той же секунды, их дата совпадает. Когда мы передаем в параметр виртуальной таблицы остатков дату:


ВЫБРАТЬ
    ЗапасыОстатки.Номенклатура КАК Номенклатура,
    ЗапасыОстатки.КоличествоОстаток КАК КоличествоОстаток,
    ЗапасыОстатки.СуммаОстаток КАК СуммаОстаток
ИЗ
    РегистрНакопления.Запасы.Остатки(&Период, Номенклатура = &Номенклатура) КАК ЗапасыОстатки

Если в &Период передана просто Дата документа, виртуальная таблица возвращает остатки на начало указанной секунды. Все документы, зарегистрированные в одну секунду, увидят один и тот же исходный остаток, полностью игнорируя движения друг друга. Смещение даты на одну секунду вперед (Дата + 1) является грубой ошибкой: при высокой интенсивности операций в эту секунду могут попасть чужие приходные накладные, что исказит учет.

Объект МоментВремени решает эту задачу. Он включает в себя не только дату и время, но и ссылку на конкретный документ (Ссылка). Это позволяет платформе однозначно упорядочивать документы на временной оси даже при абсолютно одинаковом времени создания. Передавая в запрос МоментВремени, каждый следующий документ увидит остатки с учетом движений всех предыдущих документов.

Шаг 1. Передача корректного момента времени в запрос

Посмотрим на пример правильной передачи точки среза остатков в процедуре проведения документа. В модуль проведения документа необходимо передавать свойство МоментВремени() текущего документа:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ЗапасыОстатки.Номенклатура КАК Номенклатура,
    |	ЗапасыОстатки.КоличествоОстаток КАК КоличествоОстаток,
    |	ЗапасыОстатки.СуммаОстаток КАК СуммаОстаток
    |ИЗ
    |	РегистрНакопления.Запасы.Остатки(&Период, Номенклатура В (&СписокНоменклатуры)) КАК ЗапасыОстатки";

Запрос.УстановитьПараметр("Период", ЭтотОбъект.МоментВремени());
Запрос.УстановитьПараметр("СписокНоменклатуры", ТаблицаТоваров.ВыгрузитьКолонку("Номенклатура"));

Если списание рассчитывается во внешней обработке или специализированном алгоритме, объект формируется явно через конструктор:


Момент = Новый МоментВремени(ТекДокумент.Дата, ТекДокумент.Ссылка);
Запрос.УстановитьПараметр("Период", Момент);

Шаг 2. Реализация алгоритма досписания копеек при полном выбытии товара

Даже при правильном упорядочивании документов на временной оси округление промежуточных списаний может приводить к расхождению копеек. Классическое решение этой задачи, применяемое в экзаменационных стандартах «1С:Специалист», заключается в проверке: полностью ли мы списываем остаток товара.

Разберем по шагам логику расчета себестоимости:

  1. Сравниваем количество к списанию с доступным остатком по регистру.
  2. Если количество списания в точности равно количеству остатка, мы не выполняем расчет по формуле средней, а списываем всю оставшуюся сумму целиком (СуммаОстаток).
  3. Если списывается лишь часть товара, выполняем расчет средней себестоимости с обязательным округлением до двух знаков.
  4. Обязательно контролируем, чтобы списанная сумма не превысила текущий суммовой остаток из-за погрешностей математического округления.

Рассмотрим программную реализацию этого алгоритма:


Если СтрДокумента.Количество = СтрокаОстатка.КоличествоОстаток Тогда
    // Списываем позицию под ноль: списываем весь суммовой остаток
    СебестоимостьСписания = СтрокаОстатка.СуммаОстаток;
Иначе
    // Списываем часть партии: рассчитываем среднюю
    СебестоимостьСписания = Окр(СтрДокумента.Количество * СтрокаОстатка.СуммаОстаток / СтрокаОстатка.КоличествоОстаток, 2);
    
    // Защита от превышения суммы остатка из-за округления
    Если СебестоимостьСписания > СтрокаОстатка.СуммаОстаток Тогда
        СебестоимостьСписания = СтрокаОстатка.СуммаОстаток;
    КонецЕсли;
КонецЕсли;

При таком подходе в нашем исходном примере расчет пройдет корректно:

  1. Первый документ спишет: 1 шт. и Окр(1 * 15.91 / 3, 2) = 5,30 руб. На остатке: 2 шт. и 10,61 руб.
  2. Второй документ спишет: 1 шт. и Окр(1 * 10.61 / 2, 2) = 5,31 руб. На остатке: 1 шт. и 5,30 руб.
  3. Третий документ списывает 1 шт., что равно остатку (1 шт.). Срабатывает условие равенства, и списывается весь суммовой остаток: 5,30 руб.
  4. Итог: количество = 0, сумма = 0. Зависшие копейки полностью исключены.

Важные правила надежного проведения документов

Для обеспечения стабильной работы механизма в многопользовательской среде и предотвращения других сопутствующих ошибок соблюдайте следующие правила разработки:

  1. Управляемые блокировки. Чтобы два параллельных сеанса не прочитали один и тот же остаток одновременно, перед выполнением запроса к остаткам устанавливайте исключительную блокировку на регистр накопления с помощью объекта БлокировкаДанных с отбором по списываемой номенклатуре.
  2. Предварительное сворачивание строк. Если в табличной части документа одна и та же номенклатура указана в нескольких строках, перед расчетом остатков необходимо выполнить группировку табличной части с помощью метода Свернуть() или средствами запроса, иначе первая строка не успеет зафиксировать списание для второй строки.
  3. Контроль деления на ноль и знака остатка. Алгоритм должен содержать проверку на отсутствие количественного остатка (деление на ноль) и отрицательных сумм, возникших из-за ручных корректировок.
← На главную