Шаблоны Joomla 2.5 здесь: http://joomla25.ru/shablony/

“Новая” и “старая” методики контроля отрицательных остатков при проведении документов в системе 1С:Предприятие 8.3

“Новая” и “старая” методики контроля отрицательных остатков при проведении документов в системе 1С:Предприятие 8.3

https://курсы-по-1с.рф/articles/2017-02-12-two-methods-for-inventory-check/


12.02.2017
Эта статья предназначена для внедренцев 1С – и особенно для тех, кто готовится к Аттестации на 1С:Специалист по платформе.

Сегодня мы разберем 2 методики контроля остатков – причем не только остатков на складе, но и, например, взаиморасчетов (“какова текущая задолженность клиента и можно ли отгружать ему товары”)

Обе методики применяются и в типовых конфигурациях, и в Аттестационных заданиях. И поскольку их две – нужно четко понимать, когда применима “новая” методика, а когда только “старая”.

Это базовые знания для программистов 1С, рекомендуем не оставлять пробелов в таких областях. На изучение у Вас должно уйти 15 минут :)
Постановка задачи

Возьмем простую конфигурацию с документами “Поступление товаров” и “Реализация товаров”:

Метаданные документов модельной конфигурации

Для учета остатков используется регистр накопления “Свободные остатки”:

Метаданные регистра Свободные остатки

При проведении документа “Поступление товаров” выполняются движения-приход:
Процедура ОбработкаПроведения(Отказ, Режим)
   
    Движения.СвободныеОстатки.Записывать = Истина;
    Для Каждого ТекСтрокаТовары Из Товары Цикл
        Движение = Движения.СвободныеОстатки.Добавить();
        Движение.ВидДвижения = ВидДвиженияНакопления.Приход;
        Движение.Период = Дата;
        Движение.Номенклатура = ТекСтрокаТовары.Номенклатура;
        Движение.Количество = ТекСтрокаТовары.Количество;
    КонецЦикла;

КонецПроцедуры

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

Например, на склад поступили 10 новых телевизоров LG, 6 из них было продано. Если в документе поступления 10 шт. исправить на 5 шт. – образуется отрицательный остаток «минус 1 шт.».

В типовой УТ 11 подобный контроль включается с помощью функциональной опции «Контролировать товары организаций при отмене приходов».

При проведении документа «Реализация товаров» необходимо организовать контроль остатков. Если товара на остатках недостаточно, документ не проводится и выдается диагностическое сообщение. В этом и состоит решаемая задача.

Мы намеренно работаем над простой задачей, когда себестоимость при списании не рассчитывается. Это позволит нам сосредоточиться именно на нюансах контроля остатков.
Примечание – представленные ниже алгоритмы разработаны для обучения и должны быть максимально понятными.
Их можно оптимизировать, но тогда «коэффициент понимания» будет ниже, поэтому в данной статье мы на этом не останавливаемся.

Естественно, Вы можете оптимизировать их самостоятельно, либо пройти наш курс по Ускорению и Оптимизации 1С :)

Как Вы уже поняли, решение задачи может быть выполнено двумя способами. Начнем с методики, которая применялась ещё со времен «1С:Предприятие 8.0».
Старая методика контроля остатков

Принцип старой методики контроля остатков следующий: проверяем, есть ли остаток товаров в нужном количестве. Если есть – списываем, если нет – сообщаем об ошибке.

Алгоритм в старой методике состоит из нескольких блоков:

    Запросом получаются остатки товаров и данные документа
    В цикле выполняется контроль достаточности товаров
    Если товаров недостаточно, то документ не проводится
    Если товаров достаточно – выполняются движения-расход

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

Прокомментируем ключевые точки алгоритма.
1. Очистка старых движений регистра

Ниже в алгоритме будет запрос к остаткам регистра.

Если текущий документ был ранее проведен, то существует вероятность получить в запросе старые движения документа – это серьезная проблема.

Когда возможна такая ситуация? Когда дата документа сдвигается вперед.

Покажем на примере, к чему это приведет:

    Остаток ламп настольных 10 шт.
    Проводится документ от 16.02.17, списываем 6 ламп
    В документе меняется дата на 17.02.17 (дату можно сместить хоть на 1 секунду вперед), перепроводим документ.

Если очистку движений не выполнять, то система сообщит о нехватке 2 штук. Почему? Да потому что старые движения документа списали 6 из 10 имеющихся ламп. Далее система пытается списать еще 6 штук, а на остатках есть только 4.

Проблема решается в 3 строки кода:

    Выполняется очистка набора записей (он мог быть прочитан на форме или в предыдущих обработчиках)
    У набора записей устанавливается флаг «Записывать»
    Выполняется запись всех наборов, у которых установлен флаг «Записывать»

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

Строго говоря, мы можем управлять очисткой движений при проведении документов:

Настройка режима удаления движений в документах

Вариант с удалением движений при отмене проведения является рекомендуемым – мы сами управляем, когда нужно действительно удалять движения.
2. Получение запросом данных документа и остатков регистра

Запрос состоит из двух пакетов:

    В первом получаются сгруппированные данные табличной части – создается временная таблица
    Во втором запросе к данным документа присоединяются остатки из регистра.

На что стоит обратить внимание в этом запросе:

    При создании временной таблицы индексируется поле, по которому далее будет выполняться соединение – это сделано для оптимальной производительности
    Момент получения остатков – соответствуют положению документа на временной оси
    Остатков в регистре может не быть – поэтому выполняется левое соединение и для ресурса «Количество» применяется функция «ECТЬNULL» – значение NULL приводится к нулю.

3. Обход результатов запроса
Разработанный запрос содержит сгруппированные данные документа и остатки по номенклатурным позициям.

В цикле обходим результат этого запроса.
4. Проверка на достаточность товаров
Определяем дефицит по товарам.

Если дефицит больше нуля, значит, товара не хватает:

    Выдаем диагностическое сообщение
    Выставляем параметр «Отказ» обработки проведения в значение «Истина»

Если «Отказ» будет равен «Истина», то результат транзакции проведения документа не будет зафиксирован. Говоря простым языком – это команда системе не проводить данный документ.
5. Переход в начало цикла, если были ошибки

Если на этом или предыдущих шагах цикла были ошибки (Отказ = Истина), то тогда нет смысла формировать движения. Всё равно в базу данных они не будут записаны.
6. Выполнение движений в регистры

Если проверка остатков прошла успешно, формируем движение-расход.
7. Установка флага записи движений в конце транзакции

Если данный флаг не установить, то движения НЕ будут записаны.

В конце транзакции проведения документа записываются только те наборы записей, у которых установлен флаг «Записывать».
Справедливости ради отметим, что установка свойства “Записывать” набора записей имеет смысл при одном условии – в свойстве документа “Запись движений при проведении” должно быть указано значение “Записывать выбранные”:

Свойство документа Запись движений при проведении - Записывать выбранные и Записывать модифицированные

Однако именно значение “Записывать выбранные” является стандартом де-факто:

    Оно используется в типовых решениях
    Устанавливается по-умолчанию при создании новых документов.

Другое значение свойства – “Записывать модифицированные” является устаревшим и в современных конфигурациях практически не встречается.
Новая методика контроля остатков

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

Как видите, принципиальная разница в моменте контроля остатков:

    Старая методика – сначала проверяем остаток, потом списываем
    Новая методика – сначала списываем, потом проверяем остаток.

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

Разберем ключевые точки алгоритма.
1. Получение запросом данных документа
Этот запрос нужен для группировки данных табличной части документа.

Дальше эти данные будут использованы для создания движений.

Обратите внимание, что в первом запросе пакета создается временная таблица – она будет использоваться и в следующем запросе. Это возможно благодаря менеджеру временных таблиц, который создан для этого запроса.
2. Формирование движений-расход регистра

В цикле записываются данные из документа в регистр – то есть выполняется безусловное (без проверки) списание товаров.
3. Запись движений в БД
Чтобы остатки в регистре изменились, движения нужно записать.
4. Запрос, получающий отрицательные остатки из регистра

А теперь простым запросом выбираем отрицательные остатки по товарам документа.

Именно здесь используется созданная на первом шаге временная таблица – накладывается условие на номенклатуру (для этого мы не создаем новый объект типа «Запрос», а используем созданный ранее).

Обратите внимание, как передается момент времени – используется тип данных «Граница». Остатки нужно получить на момент времени сразу ПОСЛЕ текущего документа.

Можно ли было получить остатки без границы, например, прибавив к дате документа 1 секунду?

Нет! Ведь в одной секунде может быть большое число документов. Поэтому единственный правильный вариант – использовать вид границы «Включая».
5. Вывод сообщений о недостатке товаров

Если результат запроса не пустой, значит, есть отрицательные остатки – в этом случае документ не проводится и выдаются сообщения обо всех ошибках.
Преимущества контроля остатков по новой методике

Итак, оба алгоритма решают одну и ту же задачу.

Разница между алгоритмами видна, но преимущества не очевидны.

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

    Нет необходимости очищать старые движения документа. По сути это операция записи в БД пустого набора движений и удаление существующих движений – это довольно ресурсоемкие операции
    Запрос, получающий данные по отрицательным остаткам, обращается только к одной таблице – нет необходимости делать левое соединение с данными документа и применять функцию «ЕСТЬNULL()»

Кроме этого, при нормальном течении бизнес-процессов пользователь указывает количество, не превышающее остаток на складе.

В этом случае второй запрос не вернет никаких данных и проведение документа будет максимально быстрым.

А так ли важны эти миллисекунды?

На базах с небольшим количеством данных и пользователей разница будет незаметна. Но в нагруженных системах с десятками пользователей цена каждой миллисекунды высока.

Кроме того, на экзамене 1С:Специалист по платформе нужно обязательно использовать новый способ контроля остатков, если это допускает конкретная задача.
Ok, значит, нужно всегда использовать новую методику, верно?

Нет, это не так!

Новая методика может использоваться только в том случае, если для проведения документа есть все необходимые данные в самом документе.

То есть для получения данных не нужно обращаться к регистрам, по которым производится контроль остатков.

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

Почему?
Да просто, чтобы рассчитать сумму списания себестоимости, придется обратиться к регистру. И раз уж мы делаем этот запрос ДО формирования движений, то будет иметь смысл сразу получить доступный остаток.

К слову сказать, в типовой «1С:Управление торговлей 11» реализован контроль остатков по новой методике, а в «1С:Бухгалтерии 8» – по старой методике.
Но это ещё не все!

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

Блокировкам для обоих методик контроля остатков посвящена отдельная статья. Также в данной статье мы решаем более сложную задачу – кроме контроля остатков выполняем расчет себестоимости списываемой номенклатуры. Рекомендуем её вдумчиво изучить.

А для «затравки» лишь скажем, что установка блокировки в новой методике делается очень просто – и это еще одно преимущество нового способа контроля остатков.
Итоги

Подведем краткие итоги.

Мы рассмотрели две методики контроля остатков, каждая из которых применяется в современных типовых конфигурациях.

Ключевое различие между методиками в моменте контроля остатков:

    Старая методика – контроль до записи движений в регистры
    Новая методика – контроль после записи движений в регистры

В общем случае новая методика является более эффективной, но применима она не всегда.

Критерий применимости – если для формирования движений нет необходимости обращаться к данным контролируемого регистра, можно использовать новую методику.

Если говорить о контроле остатков по номенклатуре, то применение новой методики возможно, когда данные о себестоимости и складских остатках хранятся в разных регистрах.

И в завершение примеры из типовых конфигураций:

    В УТ 11 есть 2 основных регистра для учета номенклатуры: Свободные остатки (количество) и Себестоимость товаров (данные о себестоимости) – используется новая методика
    В БП 3.0 данные о себестоимости и остатках хранятся в одном регистре бухгалтерии – используется старая методика контроля остатков.