Как выровнять партионный учет со складским в 1С 7.7 после некорректной свертки базы данных?

Программист 1С v7.7 Управленческий учет
← На главную

При работе со старыми, но до сих пор активно используемыми конфигурациями на платформе 1С 7.7 (такими как «Производство + Услуги + Бухгалтерия», или ПУБ) специалисты часто сталкиваются с техническими последствиями некорректно выполненной свертки информационной базы. Одна из самых распространенных и неприятных проблем — расхождение остатков между партионным учетом (регистр Партии) и складским учетом (регистр МестаХранения).

В этой статье мы подробно разберем, почему возникают такие расхождения, проанализируем внутреннее устройство «пустых» документов свертки и предложим проверенное на практике программное решение для полного выравнивания учета.

Выясняем причину: почему расходятся регистры и откуда берутся «пустые» документы свертки?

Проанализируем ситуацию, которая часто ставит в тупик начинающих разработчиков. База данных была «обрезана», в системе присутствует документ Свертка ИБ, который сделал движения по регистрам, но при попытке открыть его модуль проведения или форму выясняется, что они абсолютно пусты. Как такое возможно?

В платформе 1С 7.7 это стандартное следствие одного из двух подходов к свертке базы данных:

  1. Прямая запись в таблицы базы данных (SQL/DBF): Свертка выполнялась специализированной внешней утилитой. Она напрямую записала строки движений в физические таблицы регистров (таблицы с префиксами RA* и RG*) и связала их с идентификатором (ID) созданного документа-пустышки, минуя стандартный механизм проведения 1С.
  2. Удаление исходного кода после выполнения операции: Разработчик написал код проведения в модуле документа Свертка ИБ, запустил свертку, документы успешно сформировали движения. После этого код из модуля был удален (или конфигурация была обновлена на MD-файл без этого кода), чтобы пользователи случайно не перепровели документ и не уничтожили зафиксированные остатки. Физически движения в 1С 7.7 остаются в базе данных до тех пор, пока документ не будет принудительно распроведен или перепроведен.

Проблема заключается в том, что при расчете сальдо на дату свертки разработчики часто фиксируют остатки «как есть», не обращая внимания на то, что партионный и складской учет уже были разбалансированы. В результате складские остатки показывают одно количество товаров, а партионный учет — совершенно другое (либо партии вовсе отсутствуют).

Анализ стратегий решения проблемы

Рассмотрим подробнее, какие пути решения существуют в данной ситуации:

Разбор технического решения по шагам

Выполним выравнивание партий по складским остаткам. За основу мы возьмем регистр МестаХранения (складские остатки) и приведем регистр Партии в полное соответствие с ним.

Важный нюанс логики партионного учета: В складском учете нет аналитики «Партия». Нам придется «выдумать» эту аналитику. Лучший способ — в качестве партии (которая в 1С 7.7 обычно является ссылкой на документ поступления) указать сам наш корректирующий документ. При этом стоимость партий мы рассчитаем как средневзвешенную цену из существующих (пусть и кривых) партионных остатков на момент корректировки.

Ниже представлен оптимизированный и безопасный код для модуля проведения корректирующего документа. В отличие от стандартных медленных запросов 1С 7.7, здесь используется высокоскоростной метод выгрузки итогов напрямую в таблицы значений, а также добавлена защита от деления на ноль при расчете себестоимости.


Процедура ОбработкаПроведения()
    Перем ТабПартий, ТабСклада, ТЗ_Цены;
    
    РегПартии = СоздатьОбъект("Регистр.Партии");
    РегСклада = СоздатьОбъект("Регистр.МестаХранения");
    
    ТабПартий = СоздатьОбъект("ТаблицаЗначений");
    ТабСклада = СоздатьОбъект("ТаблицаЗначений");
    ТЗ_Цены   = СоздатьОбъект("ТаблицаЗначений");
    
    // Включаем временный расчет для получения итогов на нужную дату
    РегПартии.ВременныйРасчет(1);
    РегСклада.ВременныйРасчет(1);
    
    РассчитатьРегистрыНа(ДатаДок);
    
    // Выгружаем текущие итоги по обоим регистрам
    РегПартии.ВыгрузитьИтоги(ТабПартий);
    РегСклада.ВыгрузитьИтоги(TabСклада);
    
    // Шаг 1. Сторнируем (обнуляем) все текущие остатки по регистру "Партии"
    ТабПартий.ВыбратьСтроки();
    Пока ТабПартий.ПолучитьСтроку() = 1 Цикл
        // Проверяем, есть ли что сторнировать
        Если (ТабПартий.Количество = 0) И (ТабПартий.Сумма = 0) Тогда
            Продолжить;
        КонецЕсли;
        
        Регистр.Партии.Партия             = ТабПартий.Партия;
        Регистр.Партии.ВидМПЗ             = ТабПартий.ВидМПЗ;
        Регистр.Партии.МПЗ                 = ТабПартий.МПЗ;
        Регистр.Партии.МестоХранения     = ТабПартий.МестоХранения;
        Регистр.Партии.Качество         = ТабПартий.Качество;
        Регистр.Партии.СрокЭксплуатации = ТабПартий.СрокЭксплуатации;
        Регистр.Партии.Заявка             = ТабПартий.Заявка;
        Регистр.Партии.Владелец         = ТабПартий.Владелец;
        
        // Расход с тем же знаком обнуляет остаток
        Регистр.Партии.Количество         = ТабПартий.Количество;
        Регистр.Партии.Сумма             = ТабПартий.Сумма;
        Регистр.Партии.СуммаНУ           = ТабПартий.СуммаНУ;
        
        Регистр.Партии.ДвижениеРасходВыполнить();
    КонецЦикла;
    
    // Шаг 2. Расчет средних цен номенклатуры (МПЗ) для последующего распределения
    ТЗ_Цены.Загрузить(ТабПартий);
    ТЗ_Цены.Свернуть("МПЗ", "Количество, Сумма");
    ТЗ_Цены.НоваяКолонка("Цена", "Число", 15, 5);
    
    ТЗ_Цены.ВыбратьСтроки();
    Пока ТЗ_Цены.ПолучитьСтроку() = 1 Цикл
        // Защита от деления на ноль и зависших суммовых остатков без количества
        Если ТЗ_Цены.Количество > 0 Тогда
            ТЗ_Цены.Цена = ТЗ_Цены.Сумма / ТЗ_Цены.Количество;
        Иначе
            ТЗ_Цены.Цена = 0; 
        КонецЕсли;
    КонецЦикла;
    
    // Шаг 3. Заполнение партий по актуальным складским остаткам
    ТабСклада.ВыбратьСтроки();
    Пока ТабСклада.ПолучитьСтроку() = 1 Цикл
        Если ТабСклада.Количество <= 0 Тогда
            Продолжить; // Пропускаем нулевые или отрицательные складские остатки
        КонецЕсли;
        
        // Ищем среднюю цену номенклатуры
        СтрокаЦены = 0;
        СредняяЦена = 0;
        Если ТЗ_Цены.НайтиЗначение(ТабСклада.МПЗ, СтрокаЦены, "МПЗ") = 1 Тогда
            СредняяЦена = ТЗ_Цены.ПолучитьЗначение(СтрокаЦены, "Цена");
        КонецЕсли;
        
        Регистр.Партии.Партия             = ТекущийДокумент(); // Наш документ становится новой партией
        Регистр.Партии.ВидМПЗ             = ТабСклада.ВидМПЗ;
        Регистр.Партии.МПЗ                 = ТабСклада.МПЗ;
        Регистр.Партии.МестоХранения     = ТабСклада.МестоХранения;
        Регистр.Партии.Качество         = ТабСклада.Качество;
        Регистр.Партии.Владелец         = ТабСклада.Владелец;
        
        // Приравниваем количество к складскому
        Регистр.Партии.Количество         = ТабСклада.Количество;
        
        // Рассчитываем новую партионную стоимость
        НоваяСумма = Окр(СредняяЦена * ТабСклада.Количество, 2);
        
        Регистр.Партии.Сумма             = НоваяСумма;
        Регистр.Партии.СуммаНУ           = НоваяСумма;
        
        Регистр.Партии.ДвижениеПриходВыполнить();
    КонецЦикла;
КонецПроцедуры

Разберем важные подводные камни решения

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

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

    Рекомендация: После проведения выравнивания заблокируйте интерактивное изменение этого документа для пользователей (через настройку прав доступа или программный запрет в модуле формы).

  3. Восстановление последовательности: Просто выровнять остатки на дату свертки — недостаточно. Если после даты свертки в базе уже велась оперативная работа и проводились документы реализации или списания, то сразу после проведения нашей корректировки необходимо запустить восстановление последовательности партионного учета. В противном случае новые документы продолжат списывать остатки некорректно.

Альтернативное решение для баз данных на MS SQL Server (1С++)

Посмотрим на ситуацию с точки зрения производительности. Если ваша база данных 1С 7.7 работает под управлением MS SQL Server и имеет гигантский объем номенклатуры, проведение корректирующего документа стандартными средствами 1С может занять долгие часы.

В этом случае эффективнее прибегнуть к возможностям внешней компоненты 1С++ и выполнить операцию выравнивания через прямой SQL-запрос. С помощью SQL-инструкций можно за несколько секунд очистить таблицы итогов и движений регистра Партии на дату свертки и заполнить их скопированными записями из таблиц регистра МестаХранения, рассчитав рублевую стоимость по требуемому алгоритму прямо на стороне сервера. Это позволит выровнять учет мгновенно и без изменения метаданных конфигурации.

← На главную