Методика оперативного проведения и управляемые блокировки в 1С:Предприятие 8.3 (обновление 2017 года)

Для внедренцев, которые работают с типовыми или собственными конфигурациями – и тех, кто готовится к Аттестации на 1С:Специалист по платформе.

В статье мы разберем:

  • как правильно использовать управляемые блокировки при оперативном и неоперативном проведении документов
  • к чему может привести отсутствие блокировок
  • как не совершать ошибок, которые обнаружатся не сразу и могут иметь серьезные последствия :)

Время на прочтение – 20 минут.

Итак, две методики контроля остатков в 1С:Предприятии 8.3

Давайте начнем с того, что обозначения “старая методика” и “новая методика” достаточно условны. В самом деле, если “новая методика” используется с 2010 года – она уже не очень новая :)

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

“Старая методика” – это подход к контролю остатков, который использовался со времен «1С:Предприятие 8.0».

C 2010 года, с развитием платформы и добавлением новых возможностей с «1С:Предприятие 8.2» – применяется “новая методика” (однако – не везде).

В чем разница?

Принципиальная разница – в моменте контроля остатков:

  • В “старой” методике остатки контролируются ДО записи движений в регистры.
    Сначала проверяем остатки, если остатков “не хватает” (будут возникать отрицательные остатки) – проводить документ не будем
  • В “новой” методике – контроль происходит ПОСЛЕ записи движений, то есть постфактум.
    Если после проведения образовались отрицательные остатки – нужно «откатить» транзакцию, то есть отменить проведение документа.

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

Ok, значит, старая методика ушла в прошлое и это удел УТ 10.3?

Нет, это не совсем так.

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

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

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

Например, вот так:

Структура регистров

Однако встречаются конфигурации, где и количество, и стоимость учитываются на одном регистре. И вот здесь-то обоснованно остается работать старая методика контроля остатков!

Вот пример одного регистра и для количества, и для себестоимости:

Регистр накопления СебестоимостьТоваров

А что насчет типовых конфигураций? Там ведь только новая методика, верно?

Не всегда!

Вот, например, в «1C:Управление торговлей 11.3» есть 2 регистра:

Регистр СвободныеОстатки и СебестоимостьТоваров в Управление торговлей 11

При проведении документов отгрузки регистр «Себестоимость товаров» не заполняется вообще. Данные в этот регистр попадают только при выполнении регламентных операций по закрытию месяца.

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

Что касается «1C:Бухгалтерии», то там и количество, и себестоимость хранятся в одном регистре бухгалтерии, на соответствующих счетах БУ.

Поэтому в БП 3.0 используется старая методика.

Обратите внимание, что количественный и стоимостной учет в УТ 11 ведутся с разной аналитикой. Например, себестоимость дополнительно ведется в разрезе организации, подразделения, менеджера, виды деятельности НДС и так далее.

В рамках текущей статьи мы разберем блокировки и для старой, и для новой методики контроля остатков.

Про оперативное проведение документов

В этом простом вопросе часто встречаются заблуждения.

Иногда считают, что оперативное проведение – это контроль остатков по новой методике. Это не так, от слова «совсем».

Оперативное проведение можно анализировать при контроле остатков, но не обязательно.

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

Настраивается оно с помощью специального свойства документа:

Настройка оперативного проведения в 1С

Что значит «регистрировать здесь и сейчас»? Платформа для оперативно проводимых документов выполняет ряд действий:

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

Но главное другое – система передает признак оперативности проведения документа в обработку проведения:

Режим проведения документов - оперативный и неоперативный

Далее, в зависимости от значения этого параметра можно организовать запрос для получения остатков.

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

Получение остатков при оперативном проведении

Актуальные остатки хранятся в системе и получаются максимально быстро (остатки на другие даты в большинстве случаев получаются расчетным путем).

Таким образом оперативное проведение можно принять и для старой, и для новой методики контроля остатков.

Интересный факт.

В УТ 11 документам, списывающим номенклатуру, запрещено проводиться оперативно. Например, это документы «Реализация товаров и услуг», «Сборка товаров», «Перемещение товаров», «Внутреннее потребление товаров» и другие.

Почему так сделано?

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

Контроль остатков по новой методике – без блокировок

Коротко рассмотрим алгоритм контроля остатков при проведении документа «Реализация товаров и услуг» на модельной конфигурации.

Есть два регистра:

  • Свободные остатки – для количественного учета
  • Себестоимость товаров – для учета себестоимости
Структура регистров

Для контроля остатков товаров достаточно работы с регистром «Свободные остатки».

Код обработки проведения будет выглядеть таким образом:

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

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

Рассмотрим ключевые точки алгоритма контроля остатков.

1. Инициализация менеджера временных таблиц

Менеджер будет необходим, чтобы созданная в запросе временная таблица была доступна и в следующих запросах.

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

2. Запрос, группирующий данные табличной части

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

Обратите внимание, что выбирается и номер строки документа – он потребуется для контекстной привязки сообщения об ошибке. Для номера строки используется агрегатная функция МИНИМУМ() – то есть сообщение будет привязано к первой строке, где встречается указанная номенклатура.

В первом запросе пакета создается временная таблица. Во втором запросе выбираются данные временной таблицы и добавляются 2 поля, необходимые для каждой записи регистра – Период и ВидДвижения.

3. Загрузка таблицы значений в набор записей

Одной командой происходит загрузка движений в набор записей.

Плюсы такого подхода:

  • Не нужно выполнять предварительную очистку, то есть использовать метод Очистить()
  • Не нужно организовывать цикл по выборке или табличной части.
Кстати, подобный подход используется в типовых конфигурациях, в частности, в УТ 11 и БП 3.0.

4. Запись движений в БД

Запись можно было бы выполнить одной командой (вместо двух) – Движения.СвободныеОстатки.Записать().

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

Но более универсальным является такой подход:

  • Вначале установить флаг Записывать у необходимых наборов записей регистров
  • Затем вызывать метод Записать() коллекции Движения, который записывает в БД все наборы с установленным флагом Записывать

После выполнения команды «Движения.Записать()» флаг Записывать у всех наборов сбросится в Ложь.

Также нужно помнить, что в конце транзакции (после ОбработкиПроведения) система автоматически запишет в БД только те наборы записей, у которых флаг Записывать установлен в значение Истина.

В типовых решениях используется подобная схема для записи движений. Почему?

Метод Записать() коллекции Движения записывает наборы записей в одинаковой последовательности даже для разных документов.

Запись же движений вручную может привести к проблемам.

Приведем пример.

Если в документе «Реализация» выполнить запись так:

Движения.СвободныеОстатки.Записать();
...
Движения.СебестоимостьТоваров.Записать();

А в документе «Перемещение товаров» изменить порядок:

Движения. СебестоимостьТоваров.Записать();
...
Движения. СвободныеОстатки .Записать();

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

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

controlled-locks-6

5. Запрос, получающий отрицательные остатки

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

Отрицательный остаток – это и есть нехватка (дефицит) товара.

Соединение с товарами из документа выполняется только для получения номера строки.

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

6. Определение момента времени для контроля остатков

Вот здесь нам пригодилось оперативное проведение.

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

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

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

Именно в этом и заключается выигрыш оперативно проводимых документов.

7. Если запрос не пустой, значит, образовались отрицательные остатки

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

Вот так будет выглядеть диагностическое сообщение:

Сообщение о нехватке товара при проведении документа

8. Если есть ошибки, то возвращаемся из обработчика события

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

Поскольку нет смысла продолжать проведение, транзакция всё равно не будет зафиксирована (а дальше у нас будет разработан код по списанию партий).

Реализация списания себестоимости по партиям

После того, как проверка остатков прошла успешно, можно приступать к списанию партий.

Код для списания по FIFO будет таким:

//  I.  Анализ смещения даты документа вперед
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
   
    Если РежимЗаписи = РежимЗаписиДокумента.Проведение
        И НЕ ЭтотОбъект.ЭтоНовый()
        И ЭтотОбъект.Проведен Тогда
       
        Запрос = Новый Запрос;
        Запрос.Текст =
        "ВЫБРАТЬ
        |   Документ.Дата КАК Дата
        |ИЗ
        |   Документ.РеализацияТоваровУслуг КАК Документ
        |ГДЕ
        |   Документ.Ссылка = &Ссылка";
        Запрос.УстановитьПараметр("Ссылка", ЭтотОбъект.Ссылка);
        РезультатЗапроса = Запрос.Выполнить();
        ВыборкаДокумент = РезультатЗапроса.Выбрать();
        ВыборкаДокумент.Следующий();
       
        ЭтотОбъект.ДополнительныеСвойства.Вставить("СтараяДатаДокумента", ВыборкаДокумент.Дата);
       
    Иначе
        ЭтотОбъект.ДополнительныеСвойства.Вставить("ДатаДокументаСдвинутаВперед", Ложь);
    КонецЕсли;
   
КонецПроцедуры

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

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

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

Разберем ключевые точки алгоритма списания партий по FIFO.


I. Анализ смещения даты документа вперед

Здесь мы понимаем, сдвигается ли дата проведенного документа вперед. Эта информация будет полезна ниже, при очистке движений.

Для анализа сдвига даты документа потребуется 2 события:

  • Перед записью – для получения старой даты документа и проверки режима проведения документа
  • При записи – для получения новой даты документа

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

Похожий прием используется в типовой «1С:Бухгалтерии 8». Но там используется одно событие «Перед записью».

Почему в БП не нужно задействовать «При записи»?

Всё просто – документы отгрузки в бухгалтерии не могут проводиться оперативно. А это значит, что время документа не будет принимать оперативную отметку (если документ перепроводится текущим днем), поэтому и старую и новую дату документа можно получить в событии «Перед записью».

II. Подготовка наборов записей регистра «Себестоимость товаров»

Для документа установлен режим удаления движений – “При отмене проведения”:

controlled-locks-5

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

Приведем пример:

  • Остаток мониторов LG на момент проведения документов – 10 шт.
  • Проводится документ, который списывает 8 шт.
  • В этом же документе время увеличивается на 1 минуту, перепроводим

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

Примечание. Иногда встречается совет – удалять движения только при оперативном проведении.

Но это неправильно: ситуации изменения «неоперативных» документов (вчерашних и более ранних) они не учтут.

То есть проблема «нехватки 6 мониторов» (см. выше) будет в этом случае решена только для документов изменяемых сегодняшним числом.

III. Запрос, получающий остатки партий для списания по FIFO

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

На уровне итогов получается количество из документа – МАКСИМУМ(Количество) и остаток партии – СУММА(КоличествоОстаток).

Как Вы думаете, может ли количество из документа превышать суммарный остаток номенклатуры по всем партиям?

Если движения по регистрам “СвободныеОстатки” и “СебестоимостьТоваров” по количеству делаются синхронно (и приход, и расход), то такой ситуации возникнуть не может. На это мы и будем закладываться при списании партий.

IV. Цикл по номенклатуре документа

Благодаря итогам в запросе во внешнем цикле обходим номенклатуру из документа.

V. Получим количество для списания

Запомним, какое количество нужно списать. Далее это количество будет уменьшаться.

VI. Цикл по партиям по FIFO

Вложенный цикл будет содержать партии по текущей номенклатуре.

VII. Проверка на нулевой остаток

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

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

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

VIII. Расчет количества и суммы для списания

Количество для списания — это минимальное значение между остатком партии и тем, что осталось списать.

Сумма рассчитывается элементарной пропорцией.

Если списывается весь остаток партии, то будет списана и вся сумма этой партии. Это математика 3-го класса церковно-приходской школы: Х*Y/X = Y :)

То есть НЕ нужно делать дополнительных проверок (иногда дают такой совет) на то, что списывается все количество. Этот совет даже имеет своё название – «проблема копеек».

А тем, кто дает вредные советы имеет смысл заглянуть в конфигурацию «1С:Бухгалтерия 8». Там (о, ужас!) нет проверки на то, что списывается партия целиком :)

Вот скрин общего модуля «Учет товаров», метод «СписатьОстаткиТоваров»:

Проблема копеек в 1С. Решение проблемы в 1С-Бухгалтерии 8

IX. Уменьшим количество для списания

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

Зачем нужны управляемые блокировки?

Вот мы и дошли до управляемых блокировок.

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

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

Приведем пример наиболее типичной проблемы при списании товара, когда 2 пользователя практически одновременно пытаются списать товар (оформить продажу):

Проблема грязного чтения

В этом примере два пользователя почти одновременно проводят продажу товаров – документ №2 начал проводиться чуть позже документа 1.

При получении остатка система сообщает, что остаток 10 шт., и оба документа успешно проводятся. Печальный итог – на складе минус 5 мониторов LG.

Но при этом контроль остатков работает! То есть, если документ №2 будет проводиться после окончания проведения документа №1, система не проведет документ №2:

Грязное чтение, контроль остатков
Иногда встречается заблуждение – «У меня в базе одновременно работают только 3-4 пользователя, вероятность параллельного проведения документов равна нулю, поэтому на блокировки можно не отвлекаться».

Это очень опасное рассуждение.

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

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

Как решить проблему при параллельном проведении документов?

Решение простое – заблокировать мониторы LG в момент времени Т1, так чтобы другие транзакции не могли обратиться к остаткам по этому товару.

Тогда в момент времени Т2 система будет ждать, когда монитор LG будет разблокирован. И после этого система получит актуальный остаток товаров и будет выполнено (или не выполнено) списание товаров.

Буквально пару слов о классификации блокировок.

Существует 2 типа блокировок:

  • Объектные
  • Транзакционные.

Если говорить просто, то объектные блокировки не позволяют интерактивно изменить двум пользователям один объект (элемент справочника или документ).

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

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

Когда нужно накладывать блокировки?

Задача установки блокировок становится актуальной, как только в базе начинает работать более одного пользователя.

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

То есть блокировки нужно накладывать при проведении всех документов?

Ни в коем случае. Устанавливать блокировки «на всякий случай» точно не стоит. Ведь сами по себе блокировки снижают параллельность работы пользователей (масштабируемость системы).

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

В примере выше таким ресурсом является остаток по товару. Система должна была заблокировать остаток с момента получения данных об остатке (Т1) до окончания транзакции (Т3).

Примечание. Транзакция при проведении документа №1 начинается раньше, чем момент получения остатков. Но для простоты считаем, что Т1 – и время начала проведения документа, и момент получения остатков.

Пример, когда не нужно накладывать блокировку – проведение документа «Поступление товаров». В этом случае нет никакой конкуренции за ресурсы (остатки, …), поэтому блокировка будет вредна: она уменьшит масштабируемость системы.

Автоматические и управляемые блокировки

Здесь мы не будем вдаваться в теорию (это тема отдельной статьи), а скажем лишь, что управляемые блокировки являются более оптимальными.

Вместо теории можем привести пруф – все современные типовые конфигурации работают на управляемых блокировках.

Поэтому в нашей модельной конфигурации будет выбран соответствующий режим:

controlled-locks-3

Управляемые блокировки в новой технологии контроля остатков

Блокировку будем накладывать на регистр “Свободные остатки” и только на номенклатурные позиции, встречающиеся в документе.

Причем правильный вариант наложения блокировки – как можно позднее.

В новой методике контроля остатков это нужно сделать перед записью (или в момент записи) движений в регистр “Свободные остатки”, чтобы другие транзакции не смогли изменить этот разделяемый ресурс.

Блокировку можно накладывать вручную (программным образом) и чуть позже мы покажем, как это делается.

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

Нужно просто установить свойство БлокироватьДляИзменения у набора записей регистра:

    ...
    //  3. Загрузка таблицы значений в набор записей
    #Область Область3
    Движения.СвободныеОстатки.Загрузить(РезультатЗапроса.Выгрузить());
    #КонецОбласти

    //  3.1. Блокировка остатков регистра
    #Область Область3_1
    Движения.СвободныеОстатки.БлокироватьДляИзменения = Истина;
    #КонецОбласти
   
    //  4. Запись движений в БД
    #Область Область4
    Движения.СвободныеОстатки.Записывать = Истина;
    Движения.Записать();
    #КонецОбласти
    ...

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

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

Но для нашей статье принципиально следующее – система установит блокировку на комбинацию записываемых в регистр данных. А детально работу свойства БлокироватьДляИзменения мы рассмотрим в отдельной статье.

Кстати, в типовой УТ 11 не так-то просто найти установку свойства БлокироватьДляИзменения для регистра “Свободные остатки”. Дело в том, что это выполняется в модуле набора записей регистра, в событии “Перед записью”.

Вот и всё, одной строкой кода была обеспечена корректная работа системы!

Важно. Мы не накладываем блокировку на регистр “Себестоимость товаров”.

Почему? Такая блокировка являлась бы излишней (а это определенная нагрузка на сервер 1С), поскольку движения в регистры “Свободные остатки” и “Себестоимость товаров” выполняются всегда синхронно, то есть последовательно друг за другом.

Поэтому, заблокировав товары из “Свободных остатков”, мы не допустим другие транзакции до этих товаров и в регистре “Себестоимость товаров”.

Но для старой методики контроля остатков блокировка будет накладываться по-другому. Для начала разберем алгоритм партионного списания для этого случая.

Старая методика контроля остатков

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

Пусть это будет регистр “Себестоимость товаров”:

Регистр накопления СебестоимостьТоваров

Тогда алгоритм проведения документа “Реализация товаров” будет выглядеть вот так:

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

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


Процедура ОбработкаПроведения(Отказ, Режим)

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

Комментарии по ключевым моментам алгоритма.

1. Обработчик события «Перед записью»

Здесь мы понимаем, сдвигается ли дата проведенного документа вперед. Эта информация будет полезна ниже, при очистке движений.

2. Удаление «старых» движений документа

Напомним, что для документа установлен режим удаления движений – “При отмене проведения”.

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

3. Установка флага для записи движений в конце транзакции

Благодаря этому сформированные движения запишутся в момент окончания транзакции.

4. Запрос, получающий остатки по партиям на момент времени документа

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

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

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

5. Цикл по номенклатуре - проверяем достаточность количества

Именно здесь мы проверяем достаточность товаров.

Если их не хватает, то параметр “Отказ” выставляется в “Истину” и дальнейший анализ партий не будет выполняться.

6. Получим количество для списания

В отдельной переменной запомним количество для списания. Далее в цикле по партиям оно будет уменьшаться.

7. Цикл по партиям

Организуем списание по партиям.

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

8. Проверка на ноль (дальше будет выполняться деление)

Страховка от деления на ноль – очень важное дело :)

9. Расчет количества для списания

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

10. Расчет суммы списания

Сумма рассчитывается пропорцией.

11. Уменьшим количество для списания

Нужно понять, сколько еще осталось списать.

Блокировки в старой методике контроля остатков

С помощью блокировки мы как бы говорим системе, что только эта транзакция может работать с этим списком товаров (накладываем блокировку). Если другая транзакция будет пытаться наложить блокировку хоть на один из ранее заблокированных товаров, она попадет в очередь ожидания. Через некоторое время (тайм-аут) будет сделана еще одна попытка наложить блокировку.

Сейчас мы не будем предельно точно расписывать механику работы системы при ожидании на блокировке. Здесь важно другое – блокировки не позволят читать неактуальные остатки из-за параллельной работы пользователей. То есть транзакции будут выстраиваться в очередь для получения актуальных остатков.

Но при этом транзакции по непересекающемуся набору товаров будут выполняться параллельно.

В какой момент нужно накладывать блокировку?

Наша задача – получить актуальные остатки. Поэтому блокировку нужно накладывать ДО запроса, получающего остатки.

То есть в нашем коде запрос нужно разбить на два:

  • В первом получается уникальный набор товаров, помещается во временную таблицу
  • Во втором будут получаться остатки.

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

Вот так изменится код:

    //  I. Инициализация менеджера временных таблиц
    Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
    Запрос.Текст =
        "ВЫБРАТЬ
        |   РеализацияТовары.Номенклатура КАК Номенклатура,
        |   СУММА(РеализацияТовары.Количество) КАК Количество,
        |   МИНИМУМ(РеализацияТовары.НомерСтроки) КАК НомерСтроки
        |ПОМЕСТИТЬ ТоварыДокумента
        |ИЗ
        |   Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТовары
        |ГДЕ
        |   РеализацияТовары.Ссылка = &Ссылка
        |
        |СГРУППИРОВАТЬ ПО
        |   РеализацияТовары.Номенклатура
        |
        |ИНДЕКСИРОВАТЬ ПО
        |   Номенклатура
        |;
        |
        |////////////////////////////////////////////////////////////////////////////////
        |ВЫБРАТЬ
        |   ТоварыДокумента.Номенклатура КАК Номенклатура
        |ИЗ
        |   ТоварыДокумента КАК ТоварыДокумента";
       
    Запрос.УстановитьПараметр("Ссылка", Ссылка);
    РезультатЗапроса = Запрос.Выполнить();
   
    //  II. Установка блокировки
    Блокировка = Новый БлокировкаДанных;
    ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.ПартииТоваров");
    ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
    ЭлементБлокировки.ИсточникДанных = РезультатЗапроса;
    ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Номенклатура", "Номенклатура");
    Блокировка.Заблокировать();
   
    //  III. Выполняем запрос к остаткам партий  
    Запрос.Текст =
        "ВЫБРАТЬ
        |   ТоварыДокумента.Номенклатура КАК Номенклатура,
        |   ТоварыДокумента.Количество КАК Количество,
        |   ТоварыДокумента.НомерСтроки КАК НомерСтроки,
        |   ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК КоличествоОстаток,
        |   ЕСТЬNULL(Остатки.СуммаОстаток, 0) КАК СуммаОстаток,
        |   Остатки.Партия КАК Партия
        |ИЗ
        |   ТоварыДокумента КАК ТоварыДокумента
        |       ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ПартииТоваров.Остатки(
        |               &МоментВремени,
        |               Номенклатура В
        |                   (ВЫБРАТЬ
        |                       Т.Номенклатура КАК Номенклатура
        |                   ИЗ
        |                       ТоварыДокумента КАК Т)) КАК Остатки
        |       ПО ТоварыДокумента.Номенклатура = Остатки.Номенклатура
        |
        |УПОРЯДОЧИТЬ ПО
        |   Партия
        |ИТОГИ
        |   МАКСИМУМ(Количество),
        |   СУММА(КоличествоОстаток)
        |ПО
        |   НомерСтроки
        |АВТОУПОРЯДОЧИВАНИЕ";

Прокомментируем ключевые моменты.

I. Инициализация менеджера временных таблиц

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

II. Установка блокировки

К этому мы шли всю статью.

Товары для блокировки берутся из результата запроса. Далее сопоставляются поле из регистра и поле запроса (в нашем случае оба называются – Номенклатура).

III. Выполняем запрос к остаткам партий

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

Подведение итогов

Установку управляемых блокировок нужно обязательно выполнять на конкурирующие ресурсы, которые используются в транзакциях (в частности при проведении документов).

Мы рассмотрели установку блокировок для старой и новой методики контроля остатков:

  • В новой методике используется свойство БлокироватьДляИзменения набора записей регистра
  • В старой – запросом получаются данные из табличной части и программно устанавливается блокировка до получения остатков

Новая методика используется в УТ 11, старая – в БП 3.0.

Но это не значит, что в УТ 11 не накладываются блокировки программным образом. Например, для регистров взаиморасчетов используется блокировка через объект “Блокировка данных”.

И не забывайте про блокировки на экзамене “1С:Специалист” по платформе! :)

Выгрузки ИБ и PDF-версия статьи для участников группы ВКонтакте

Мы ведем группу ВКонтакте – http://vk.com/kursypo1c.

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


Статья в PDF-форматеСтатья в PDF-формате
Вы можете скачать эту статью в формате PDF по следующей ссылке: Ссылка доступна для зарегистрированных пользователей)
ИБ с новой методикой контроля остатков:
Ссылка доступна для зарегистрированных пользователей)
ИБ со старой методикой контроля остатков:
Ссылка доступна для зарегистрированных пользователей)

ИБ разрабатывались на платформе 1С:Предприятие 8.3.9.1850.

113 комментариев к “Методика оперативного проведения и управляемые блокировки в 1С:Предприятие 8.3 (обновление 2017 года)

  1. Дмитрий сказал:

    Евгений, добрый день! Спасибо за подробную статью.

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

    при этом возможен случай, когда в табличной части документа будет несколько строк с одинаковой номенклатурой, с заполненной партия не пустой(система должна правильно выбрать партию).
    Спасибо!

    • Евгений Гилев (Мастер-тренер) сказал:

      Добрый день!
      Для решения этой задачи нужно изменить запрос по партиям.
      Код приводить не буду, дам лишь общее направление.
      В запросе добавляете ещё одно соединение табличной части документа и регистром партии. По полям Номенклатура и Партия.
      Меняете сортировку. Строки с конкретной партией должны находиться выше. Для этого можно добавить поле, в котором указано 1, если партия в документе заполнена. И 0, если не заполнена.
      Также нужно отдельно хранить, сколько позиций каждой партии уже списано. Ведь одна и та же партии на нижнем уровне итогов может встретиться несколько раз. Либо учесть это в запросе, что сложнее.

  2. tik-tak-ololo сказал:

    Добрый день!
    // II. Подготовка наборов записей регистра “Себестоимость товаров” #Область ОбластьII Если ДополнительныеСвойства.ДатаДокументаСдвинутаВперед Тогда Движения.СебестоимостьТоваров.Записывать = Истина; Движения.СебестоимостьТоваров.Очистить(); Движения.Записать(); КонецЕсли; Движения.СебестоимостьТоваров.Записывать = Истина; #КонецОбласти

    Скажите, зачем здесь используется метод набора записей Очистить() ?

    Набор ведь не прочитан, значит он пустой. Разве недостаточно:
    Движения.СебестоимостьТоваров.Записывать = Истина;
    Движения.Записать();
    ???

    • Евгений Гилев (Мастер-тренер) сказал:

      Добрый день!

      В общем случае набор может быть не пустой.
      Например, если набор записей находится на форме (как в документе Бухгалтерская операция).

      То есть мы привели наиболее общее решение. В частных случаях набор записей можно не очищать.

  3. Игорь сказал:

    какой в итоге точке зрения придерживаться если у каждого своя правда

  4. Игорь сказал:

    Здравствуйте,
    Чистов, в своих уроках делал акцент на обязательную проверку списания всего остатка партии, объясняя это тем, что на экзамене за отсутствие этого пункта снимают пол бала, сказано воспринимать это как аксиому, в действительности алгоритм и без нее отработает верно, но 1с настаивает о ее наличии.

    • Евгений Гилев (Мастер-тренер) сказал:

      Доброго дня!

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

  5. Henkin сказал:

    Здравствуйте!
    Мне кажется, проверку необходимости записи пустого набора в регистр “СебестоимостьТоваров” можно осуществить проще:

    Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    ДополнительныеСвойства.Вставить(“БылПроведен”, Проведен);
    ДополнительныеСвойства.Вставить(“ДатаСтарая”, Дата);
    КонецПроцедуры

    Процедура ПриЗаписи(Отказ)
    ДополнительныеСвойства.Вставить(“ДатаНовая”, Дата);
    КонецПроцедуры

    И фрагмент из “ОбрабокаПроведения”. что-то наподобие:

    Движения.СебестоимостьТоваров.Записывать = Истина;
    Движения.СебестоимостьТоваров.Очистить();
    Если ДополнительныеСвойства.БылПроведен И (ДополнительныеСвойства.ДатаСтарая < ДополнительныеСвойства.ДатаНовая) Тогда
    Движения.СебестоимостьТоваров.Записать();
    КонецЕсли;

    Или я что-то упускаю?

    • Евгений Гилев (Мастер-тренер) сказал:

      Добрый день!

      Да, Ваш вариант сработает.

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

    • Евгений Гилев (Мастер-тренер) сказал:

      В конце статьи можно скачать рабочие базы.
      Листинги взяты из этих баз.

  6. Николай сказал:

    Вопрос по старой методике.

    Если у нас есть регистр остатков количества, например
    Остатки(
    Номенклатура
    Количество)

    и регистр расчета себестоимости, например
    Себестоимость(
    Номенклатура,
    Количество,
    Сумма)

    то при расходе будет ли достаточно заблокировать только регистр Остатки и если количества хватает, то себестоимость рассчитать и списать пост-фактум или же необходимо блокировать и регистр Остатки и регистр Себестоимость?

    • Евгений Гилев (Мастер-тренер) сказал:

      Ответ на вопрос будет зависит от окружения. А именно, от того, какие документы делают движения по регистру “Себестоимость”.
      Если рассмотреть случай, когда движение делает только документ продажи “Реализация товаров”, то достаточно наложить блокировку на регистр “Остатки”.

  7. I-am-a-programmer.ru сказал:

    Евгений, спасибо за ответы!

    У меня остался не ясным последний вопрос по новой методике контроля остатков. Предположим, что у нас спланирован регистр накопления

    ОстаткиОборудования
    Изм: Номенклатура
    Изм: СрокГодности
    Ресурс: Количество

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

    Можно ли в этом случае использовать новую методику проведения?

    Ведь при записи расходных движений мы можем указать в реквизите СрокГодности любую дату, хоть дату документа или пустую дату, при этом остаток проверять только по Номенклатуре и Количеству. И если количества достаточно, то уже новым запросом получить корректные остатки номенклатуры по срокам годности и сделать правильное списание.

    • Евгений Гилев (Мастер-тренер) сказал:

      Добрый день!

      Приведенный алгоритм нельзя считать оптимальным.

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

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

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

  8. L1ght_Inoy сказал:

    Отличная статья. Спасибо. Все протестировал, все понятно, один момент у меня только не взлетает. Последний пример с упр. блокировкой для старой методики.
    Две одинаковых расходных.
    1. Запускаю первый сеанс, провожу РН1 и ставлю точку останова на РезультатЗапроса перед описанием блокировки.
    2. Запускаю второй сеанс, провожу РН2 и он встает в очередь, т.к. очистка движений в РН1 наложила блокировку. Все ок.
    3. Далее F5, чтобы допровелся первый документ и в первом сеансе вылетает ошибка, при этом второй сеанс проводит РН2 без ошибок.

    Вот ошибка.
    Ошибка при выполнении обработчика – ‘ОбработкаПроведения’
    по причине:
    {Документ.РасходнаяНакладная.МодульОбъекта(192)}: Ошибка при вызове метода контекста (Заблокировать)
    Блокировка.Заблокировать();
    по причине:
    Конфликт блокировок при выполнении транзакции:
    Неустранимый конфликт блокировок

    Вот код, как в примере.

    Если ЭтотОбъект.ДополнительныеСвойства.ДатаДокументаСдвинутаВперед Тогда
    Движения.Себестоимость.Записывать = Истина;
    Движения.Себестоимость.Очистить();
    Движения.Записать();
    КонецЕсли;
    Движения.Себестоимость.Записывать = Истина;

    Запрос = Новый Запрос;
    Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
    Запрос.Текст =
    “ВЫБРАТЬ
    | РасходнаяНакладнаяТовары.Номенклатура КАК Номенклатура,
    | СУММА(РасходнаяНакладнаяТовары.Количество) КАК Количество,
    | МИНИМУМ(РасходнаяНакладнаяТовары.НомерСтроки) КАК НомерСтроки
    |ПОМЕСТИТЬ ТЧТовары
    |ИЗ
    | Документ.РасходнаяНакладная.Товары КАК РасходнаяНакладнаяТовары
    |ГДЕ
    | РасходнаяНакладнаяТовары.Ссылка = &Ссылка
    |
    |СГРУППИРОВАТЬ ПО
    | РасходнаяНакладнаяТовары.Номенклатура
    |
    |ИНДЕКСИРОВАТЬ ПО
    | Номенклатура
    |;
    |
    |/////////////////////////////////////////////////////////////////// |ВЫБРАТЬ
    | ТЧТовары.Номенклатура КАК Номенклатура
    |ИЗ
    | ТЧТовары КАК ТЧТовары”;

    Запрос.УстановитьПараметр(“Ссылка”, Ссылка);

    РезультатЗапроса = Запрос.Выполнить();

    Блокировка = Новый БлокировкаДанных;
    ЭлементБлокировки = Блокировка.Добавить(“РегистрНакопления.Себестоимость”);
    ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
    ЭлементБлокировки.ИсточникДанных = РезультатЗапроса;
    ЭлементБлокировки.ИспользоватьИзИсточникаДанных(“Номенклатура”, “Номенклатура”);
    Блокировка.Заблокировать();

    • Евгений Гилев (Мастер-тренер) сказал:

      Используете клиент-серверный вариант работы 1С? Блокировки в базе управляемые?
      И на всякий случай попробуйте на последнем релизе платформы 8.3.10.

      • L1ght_Inoy сказал:

        Клиент-серверный, управляемые, платформа – (8.3.9.2033).
        ОК, попробую, покопаюсь, спасибо)

        • Евгений Гилев (Мастер-тренер) сказал:

          Прямо сейчас не готов смоделировать.

          Напишите результаты своих экспериментов :)

  9. Афанасьев Евгений сказал:

    УТ 10.3.25, 8.3.8, автоматически блокировки. Перешли на PostgreSQL.
    Конфликтов блокировок стало гораздо больше. Что посоветуете? (Переход на MS SQL не вариант).
    Как я понимаю, вариант один – переход на упр. блокировки?!
    Как поведет себя система, если в свойствах конфигурации установить “упр. блокировки”, но проведение документов пока оставить без изменения?! Т.е. пока не устанавливать блокировку “Блокировка = Новый БлокировкаДанных;”

    • Андрей Бурмистров сказал:

      В автоматическом режиме блокировок при работе с версионной СУБД (каковой и является PG) будет блокироваться вся таблица целиком. Поэтому для многопользовательской работы этот режим не подходит. Вам конечно надо переходить на управляемые блокировки.
      Что бы это сделать вам необходимо понимать как именно работают блокировки в разных режимах, когда и зачем они накладываются. Все это невозможно описать в одном комментарии, для этого нужно пройти курс по оптимизации.
      Могу описать общую концепцию, в копии базы вы ставите режим управляемый на всю конфигурацию. Далее в тех местах, где идет чтение, а потом запись прочитанных данных (например контроль остатков по старой схеме) вы перед читающим запросом ставите явную упр. блокировку. Либо, если позволяет бизнес-логика вы используете новый способ контроля остатков, сначала запись потом чтение, тогда явная упр. блокировка не требуется. В конце вы пробуете списать товары в минус в параллельных транзакциях. Эти действия нужно повторить по каждому регистру по которому у вас есть контроль остатков.

      • Афанасьев Евгений сказал:

        Если в свойствах укажу режим только “Управляемый”, то поведение системы поменяется? Или по прежнему будет блокировать вся таблица, пока я не допишу “программные блокировки”?

        • Андрей Бурмистров сказал:

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

          • Афанасьев Евгений сказал:

            Благодарю.
            Значит, буду включать смешанный и постепенно переводить.

      • Афанасьев Евгений сказал:

        И вот кстати проблема, пример. Сейчас режим “Автоматический и управляемый”.
        В УТ 10.3 добавлен модуль версионирования (взят из БСП 2.2.1).
        Так вот у номенклатуры есть подписка на событие “ЗаписатьВерсиюОбъекта” (перед записью). В обработчике добавляется запись в регистр “ВерсииОбъектов” (режим поставил “упр.”). Добавил код в процедуру “Перед записью” набора. Но все равно блокируется вся таблица =(

        http://joxi.ru/V2VYaZbsxW05km

        • Андрей Бурмистров сказал:

          Если режим конфигурации смешанный, тогда будет работать тот режим, который был у первой транзакции. Например, если документ в автоматическом режиме, а регистр в управляемом, то регистр тоже будет записан в упр. режиме.
          Тот код который вы написали в регистре вообще не имеет смысла, набор записей и так будет заблокирован исключительной управляемой блокировкой при записи, это система сделает автоматически.

  10. I-am-a-programmer.ru сказал:

    Теперь на экзамене необходимо контролировать смещение даты вперед или для экзамена подойдет вариант с очисткой движений без контроля смещения?

    • Евгений Гилев (Мастер-тренер) сказал:

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

      • I-am-a-programmer.ru сказал:

        Евгений, ещё один вопрос рядом с темой.
        Очень нравится вот такая конструкция:
        ————————————-
        #Область Область3 Движения.СвободныеОстатки.Загрузить(РезультатЗапроса.Выгрузить());
        #КонецОбласти
        ————————————
        но не будет ли она считаться нарушением требования на 1С Специалист: выгрузка результата запроса в промежуточную таблицу (например, в таблицу значений) без необходимости” – минус 1 балл?

        • Евгений Гилев (Мастер-тренер) сказал:

          Нет, не будет.

          Но на любом экзамене нужно уметь защищать выбранную схему решения (знать ответ на вопрос – “А зачем вы так сделали?”).

          Здесь обоснование такое:
          1. Выгрузка результата запроса в таблицу значений эффективный, чем перебор в цикле
          2. Загрузка в набор записей эффективней добавления движений в цикле.

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

  11. User12345 сказал:

    Добрый день! В разделе “Реализация списания себестоимости по партиям” получается дата документа запросом:
    Запрос = Новый Запрос;
    Запрос.Текст = “ВЫБРАТЬ
    | Документ.Дата КАК Дата |ИЗ | Документ.РеализацияТоваровУслуг КАК Документ
    |ГДЕ
    | Документ.Ссылка = &Ссылка”; Запрос.УстановитьПараметр(“Ссылка”, ЭтотОбъект.Ссылка);
    РезультатЗапроса = Запрос.Выполнить();
    ВыборкаДокумент = РезультатЗапроса.Выбрать();
    ВыборкаДокумент.Следующий();

    Не будет ли проще получить дату напрямую через ссылку:
    ДатаДокумента = ЭтотОбъект.Ссылка.Дата ?

    • Вячеслав Вязигин сказал:

      День добрый!

      Конечный результат в обоих случаях конечно будет одинаковый, но на уровне СУБД будут выполнены разные действия.

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

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

  12. White сказал:

    Вопрос к Евгению.
    Для расходного документа в модуле объекта прописан код вычисляющий был ли сдвиг даты документа вперед. Теперь пример, открываю существующий расходник и перепровожу его. Т.к. он проводится оперативно, то дата у него становится текущей. На лицо сдвиг вперед. Но если отладчиком посмотреть значение переменных в обработчике события “ПередЗаписью”, то в переменной “Дата” дата старая со “старыми” секундами. Из запроса получаем эту же старую дату и флаг сдвига устанавливается Ложь. В обработке проведения движения не очищаются, но дата документа становится текущей, новой. Остатки при проверке включают в себя старые движения,т.к. движения дока не очистили. Наглядно убедиться можно при работе старой методики. Сделать приход на 4 единицы товара. Сделать расходник на 3 ед. Через секунду перепровести и будет ошибка о дефиците товара в 2 единицы.
    Может зря придираюсь и в статье показан просто принцип, что чистить старые движение можно не всегда?

    • Евгений Гилев (Мастер-тренер) сказал:

      Доброго дня!

      Любые конструктивные дискуссии на эту тему будут полезны. Причем большому количеству участников – многие читают комментарии.

      Поэтому с удовольствием отвечаю :)

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

      Документы в этой базе (как и в 1С:Бухгалтерии) проводятся неоперативно. Поэтому описанной Вами проблемы там быть не могло.

      Но в данной статье мы смотрим шире, и рассматриваем в т.ч. оперативное проведение.

      И чтобы оценить сдвиг даты документа в случае перепроведения документа текущим днем потребуется два события:

      • Перед записью – для получения старой даты документа
      • При записи – для получения новой даты документа

      Именно так сейчас и представлены алгоритмы проверки, посмотрите сюда:
      http://курсы-по-1с.рф/articles/2017-02-13-realtimeposting-and-datalock/#shift_date_of_document

      • White сказал:

        ПДФ сохраняю, а изменений нет про событие “ПриЗаписи”. Такой неактуальный ПДФ я и читал)

        • Евгений Гилев (Мастер-тренер) сказал:

          Вы посмотрите на сайте по моей ссылке.
          PDF обновляется не сразу.

          • White сказал:

            Остался последний вопрос)) Мне кажется, точнее я практически уверен, что в случае новой методики необходима исключительная блокировка (объект БлокировкаДанных) на регистр “себестоимость товаров”. Вставить после формирования движений по “Свободные остатки”, но перед формированием движений по “СебестоимотьТоваров”. Верно?

            • Евгений Гилев (Мастер-тренер) сказал:

              Необходимость блокировки регистра “Себестоимость товаров” зависит от структуры данных/алгоритмов прикладного решения.
              Для приведенной в статье конфигурации такая блокировка будет излишней – ведь необходимый набор данных заблокируется на уровне регистра “Свободные остатки”.
              А “лишняя” блокировка это дополнительная нагрузка на сервер приложений.

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

              Так, например, разработчикам УТ 11/ERP удалось избежать блокировки регистра “Себестоимость товаров” при проведении реализации.

  13. Bennington сказал:

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

    Слегка запутали. В первом пункте говорится, что БлокироватьДляИзменения отключает режим разделения итогов( если отключен режим разделения итогов у регистра, то блокируется весь регистр до окончания транзакции, верно?). И если смотреть на пункт 2, то как мы можем заблокировать состав регистра по отдельному набору записей, если мы отключили разделение итогов?

    • Евгений Гилев (Мастер-тренер) сказал:

      если отключен режим разделения итогов у регистра, то блокируется весь регистр до окончания транзакции, верно?

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

      А если режим разделение итогов был бы НЕ отключен – блокировка бы вообще не наложилась. И в любая другая транзакция могла бы проводить документы по такому же набору измерений.

      Детальнее про этот механизм в отдельной статье:
      http://курсы-по-1с.рф/news/2017-03-09-how-attribute-lock-to-change-works/

  14. marivgo сказал:

    Спасибо большое! Очень полезная статья, и как раз вовремя. Вы лучшие:)))

  15. vic777tor сказал:

    Спасибо, Евгений, за очень полезную статью!
    Как всегда, все четко и ясно, разложено “по полочкам”.
    Особенно ценно то, что все нюансы сведены воедино и дают четкое представление о взаимодействии движений по регистрам, остаткам и блокировкам.
    Порадовала трактовка «проблемы копеек» – это как раз мой случай :)
    Несколько лет назад тщетно пытался на экзамене по платформе доказать преподавателю бессмысленность “контроля копеек” при списании себестоимости товаров в “0”. Теперь же получил хотя бы моральную компенсацию ;)

    • Евгений Гилев (Мастер-тренер) сказал:

      Отлично :)

      На тему “проблему копеек” намечается еще одна статья. Там мы эту тему под микроскопом изучим :)

      • Evilgrym сказал:

        Копейки – зло, особенно когда на приходе сумма/количество не дает цену с 2 знаками. А потом этот товар еще и дробится. Характерная беда в фарм рознице

    • Четверг сказал:

      Если для экзамена, то лучше наверное проблему копеек решать через условие Если. Меньше проблем будет:
      – отсутствие конфликтов с экзаменатором;
      – есть задачи, при использовании алгоритма сначала умножение, потом деление, проблема может остаться (есть там несколько таких замудренных задач). А подгонять запрос, чтоб решить проблему копеек именно этим алгоритмом как то не хочется. Т.е. там и так времени мало, лучше делать по одной методике (быстрее будет на экзамене и меньше риск ошибиться на дурацкой задачке).

  16. Сержантов Владимир Викторович сказал:

    Добрый день!
    1.Вопрос к странице 12. На ней объясняется новый подход к записи документа и приведен скриншот документа реализации со выключенным свойством “Оперативное проведение”, а в пункте 6 идет проверка на режим проведения. Разве он не всегда неоперативный?
    2. При контроле остатка старым способом (списание количества и суммы из одного регистра), был использован цикл в цикле. Это допускается на экзамене 1С Специалист? Знаю, что это встречается и в типовых конфигурациях, просто хотел знать

    3. При обнаружении дефицита, используется “Продолжить”, разве не было бы более правильным, вообще не дать проводить документ, а не списывать частично?

    • Евгений Гилев (Мастер-тренер) сказал:

      Добрый день!

      1. Заменим скриншот, на этом скриншоте оперативное проведение не важно, есть смысл в другом :)

      2. Два вложенных цикла по запросу с итогами – вполне нормальная ситуация.
      На экзамене карается запрос в цикле, здесь такого нет.

      3. Частичного списания выполнено не будет. Так как параметр Отказ устанавливается в Истина.
      А с помощью продолжения цикла, мы выведем сразу все возможные ошибки, чтобы пользователь мог их исправить за раз.

  17. Andrig сказал:

    Старая методика контроля остатков.
    Итоги по НомерСтроки или Номенклатура?

    МАКСИМУМ(Количество),
    | СУММА(КоличествоОстаток)
    |ПО
    | НомерСтроки”;

    • Евгений Гилев (Мастер-тренер) сказал:

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

  18. Makushimo сказал:

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

    Движения.СвободныеОстатки.Записать() – и так наложит Х-блокировку на набор движений до конца транзакции.

    или ошибка в статье или в курсе по оптимизации. )))

    • Евгений Гилев (Мастер-тренер) сказал:

      Добрый день!

      Это прекрасно, что Вы знаете технологические нюансы. На уровне этой статьи мы их осознанно опустили, чтобы рассмотреть в отдельной статье :)

      • Makushimo сказал:

        Правильно ли я понимаю, что при выключенном разделителе итогов запись .БлокироватьДляИзменения = Истина будет проигнорирована?

        • Евгений Гилев (Мастер-тренер) сказал:

          Немного не так.
          При выключенном разделении итогов блокировка будет наложена безусловно, вне зависимости от этого флага.
          Но даже в этом случае рекомендуется использовать БлокироватьДляИзменения – ведь рано или поздно найдется оптимизатор, который включит разделение итогов :)

          • Makushimo сказал:

            Именно это я и имел ввиду.
            Вы рекомендуете ставить БлокироватьДляИзменения впрок.

              • Сергей сказал:

                Таким образом, при новой методике проведения наложение блокировок необязательно? И на экзамене на спеца можно объяснить почему было принято решение не накладывать блокировку?

                • Евгений Гилев (Мастер-тренер) сказал:

                  Если речь об отдельной управляемой блокировке для новой методике, то да.

                  НО, не все задачи решаются по новой методике контроля остатков.

          • Daip сказал:

            Евгений, еще вопрос. А автоматическая безусловная блокировка будет исключительной или разделяемая?
            Заранее спасибо!

  19. Четверг сказал:

    в какой момент накладывать блокировки (после Движения.Записать() (записи пустого набора) или до)? Вроде все вначале очищают старые движения (Движения.Записать()), потом устанавливают блокировки. Это не вызовет никаких доп проблем, типа Два пользователя успели удалить старые движения по двум документам, потом один из них установил блокировку и начал анализировать удаленные первым пользователем остатки, хотя он еще транзакцию не завершил.

  20. Четверг сказал:

    “А тем, кто дает вредные советы имеет смысл заглянуть в конфигурацию «1С:Бухгалтерия 8». Там (о, ужас!) нет проверки на то, что списывается партия целиком :)”
    На экзамене по платформе есть мудреные задачи, где этот алгоритм не будет срабатывать. Точно не помню, там есть задачи где уменьшается не только количество (ОсталосьСписатьКоличество), но и стоимость (ОсталосьРаспределитьСумму). В этом случае копейки зависнут, поэтому используют условие Если Движение.Количество=ОсталосьСписатьКоличество Тогда Движение.Сумма=ОсталосьРаспределитьСумму.
    Давно это было, вроде 5 билет задача по БухУчету.

    • Евгений Гилев (Мастер-тренер) сказал:

      В таких задачах очевидно, что нужно заводить отдельную переменную для списания суммы.
      Но мы часто встречаемся с тем, что это “проверку” использует всегда. Бессмысленность этого и хотелось подчеркнуть.

  21. Dach сказал:

    В статью необходимо добавить несколько фраз о том, что “новая” методика проведения применима только для платформы 8.3 и для ИБ, не использующихся на ней в режиме совместимости 8.2. Так как на платформе 8.2 запрос к остаткам, находящийся внутри транзакции – не сможет прочитать “незакоммиченные” данные (так как уровень изоляции транзакций у 8.2 ReadCommited). Получается, мы заблокировали чтение остатков для всех запросов, которые “сидят” внутри транзакций, в том числе и для нашего собственного. Это легко проверить, обернув любой запрос в транзакцию и начав паралелльно проводить документы – в сеансе запроса словим блокировку по таймауту. “Грязные” данные 8.2 умеет читать только вне транзакций (пример – отчеты спокойно работают и во время проведения документов). На 8.3 уровень изоляции транзакций менее жестокий – Read Commited Snapshot, что позволяет внутри транзакций читать “грязные”, “незакоммиченные” данные. И именно поэтому, кстати, блокировку надо ставить не ПОСЛЕ записи движений, а ДО. Иначе все остальные транзакции успешно спишут мониторы и ни одной из них мониторов не хватит. Или наоборот – хватит. Так вот, блокировка в данном случае от чтения “грязных” данных не спасет и вы спишете 24 монитора.

    И еще. Все вышеозначенные подходы, как старый, так и новый не применимы на высоко нагруженных тяжелых системах. Представим, что вы записываете 100 тыс. строк движений. Все остальные сеансы просто не дождутся, пока у первого закончится запись, контроль и коммит транзакции и тупо отвалятся по таймауту… Кроме того, такая блокировка может вызвать еще и лавинообразную эскалацию блокировок и вообще деградацию всей системы.

    Понимаю, что мой коммент нескольо за гранью Вашей статьи, Евгений, но почему никто из тех, кто уже ломал копья на этой благодатной почве – ни Павел Чистов, ни сама 1С даже на своих курсах – никто не объясняет саму суть, почему вообще родился новый подход, почему он стал возможен. А без этого понимания пишется такой код, что никакие ЦУПы и профайлеры не спасают )))

    • Евгений Гилев (Мастер-тренер) сказал:

      Добрый день, Роман!

      Предлагаю Вам развить мысль в отдельной статье.
      Получится очень логичное углубление в интересную тему. Барышни сегодня свяжутся с Вами.

      Только один момент – сама по себе новая методика контроля остатков появилась как раз с выходом платформы 8.2 (в далеком 2010 году).
      Как раз, когда появилась возможность записывать выбранные наборы записей и добавилось свойство БлокироватьДляИзменения.

  22. Сергей сказал:

    Большое спасибо. Понравилась идея про контроль смещения даты вперед.
    Но немного непонятно, разве вот тут:

    Если ДополнительныеСвойства.ДатаДокументаСдвинутаВперед Тогда Движения.СебестоимостьТоваров.Записывать = Истина; Движения.СебестоимостьТоваров.Очистить();
    Движения.Записать();
    КонецЕсли;

    Не надо перед записью добавить: Движения.СебестоимостьТоваров.БлокироватьДляИзменения = Истина ?

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

  23. carapuzzz сказал:

    Евгений, БлокироватьДляИзменения = Истина не устанавливает блокировку, а лишь отключает режим разделения итогов, если таковой установлен для регистра, если же этот режим не установлен, то смысла в этой строке нет, т.к. просто ничего не произойдет. Т.е. блокировки накладываются автоматически в момент записи регистра всегда. Считаю, что про разделение итогов стоило упомянуть в этой статье.

    • Евгений Гилев (Мастер-тренер) сказал:

      На самом деле, это технологические моменты, на которые сейчас не хотелось обращать внимание.
      Однако, знать про это полезно. И это, пожалуй, мы сделаем в отдельной статье :)

      На уровне этой статьи достаточно знать того, что написано в синтакс-помощнике про свойство “БлокироватьДляИзменения”:

      Устанавливает режим, при котором в процессе записи набора будет установлена управляемая блокировка для всех комбинаций измерений в соответствии с записями набора записей. Имеет смысл использовать, если проверка итогов регистра выполняется после записи и заблокировать нужно именно те комбинации, по которым записываются записи. В этом случае можно не использовать объект БлокировкаДанных.

  24. Pr0glam3er сказал:

    Добрый день!
    Смоделируем ситуацию. Товара Т1 на складе нет.Документ №1 уже был проведен и списал 10 единиц товара Т1. Пользователь начинает перепроводить этот документ и меняет товар Т1 на товар Т2. В этот же момент времени другой пользователь проводит другой документ по товару T1, а мы его не заблокировали. Первая транзакция отменяется, вторая – успешная. Получается отрицательный остаток. Вопрос – как правильно накладывать блокировки на движения, которые документ уже совершил?

    • Евгений Гилев (Мастер-тренер) сказал:

      Добрый день!

      Хороший вопрос :)

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

      При удалении движений автоматически будет наложена и исключительная управляемая блокировка и X блокировка СУБД. То есть ничего вручную блокировать не нужно. Обе эти блокировки будут держатся пока транзакция не завершится (до конца проведения).

      Получается, что второй документ не сможет списать товар Т1, так как он заблокирован первым документом и блокировка будет держатся до завершения транзакции.

      • psy_sln сказал:

        т.е. получается, в вашем примере, если дата документа изменена, блокировка будет наложена автоматически(при очистке движений), а затем еще раз накладывается управляемая блокировка?

        • Евгений Гилев (Мастер-тренер) сказал:

          Блокировка при очистке будет наложена на удаляемые записи.
          Вручную блокировка накладывается на записи документа, которые могут отличаться от “прошлого” состояния документа.

      • Alexander Fokin сказал:

        В первой сессии
        1 открываю транзакцию
        2 очищаю движения по Товару1
        3 замираю

        а) Во второй сессии
        1 открываю транзакцию
        2 пытаюсь заблокировать регистр по Товару1 – не получается, все замечательно…

        б) Во второй сессии
        1 Открываю транзакцию
        2 Выполняю проведение по регистру по Товару1 без установки управляемой блокировки – документ проводится – НЕПОРЯДОК?

        • Евгений Гилев (Мастер-тренер) сказал:

          Кажется, что порядок :)

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

          В этом сценарии в первой сессии может быть документ “Реализация товаров”.
          Во второй – “Поступление товаров”.
          Так?

          Почему же в этом случае нужно блокировать проведение документа поступление товаров? Если включен режим разделения итогов, то документ проведется и это правильно.

          Если Вы говорите о другом сценарии – приведите его.

          • Alexander Fokin сказал:

            1. Ваше объяснение мне нравится. Но если вместо прихода кто-то делает СПИСАНИЕ товара ручной операцией?

            2. Кстати, правильно же я понимаю, что и наложение ЯВНОЙ управляемой блокировки может не спасти от отгрузки в минус (при использовании ручной операции)?

            • Евгений Гилев (Мастер-тренер) сказал:

              Ответы на оба вопроса зависят от того, как у Вас реализована ручная операция.
              Но при этом мы считаем, что в регистре включено разделение итогов. В противном случае исключительную блокировку по Товару1 наложит первая транзакция, остальные будут её ожидать.

              Давайте, представим, что есть 20 телевизоров на складе, в ручной операции списываем 30 шт.
              Что произойдет?

              Есть 2 варианта:
              1. В операции нет никаких проверок, остаток будет -10 шт.
              2. Есть проверки в модуле набора записей регистра, транзакция не будет зафиксирована.

              Если у Вас реализован первый вариант, то в приведенном выше примере (когда параллельно товар списывается “Реализацией” и ручной операцией) будет списание в минус. Но, раз нет никаких проверок, это не удивительно.

              Если сделан второй вариант, то перед чтением остатков нужно будет установить управляемую блокировку. Но этого система не позволит сделать, так как в этом примере на Товар1 наложена блокировка первой транзакцией.

              А вообще про БлокироватьДляИзменения и режим разделения итогов у нас на этой неделе должна появиться новая статья. Вам она будет интересна ;)

              • Alexander Fokin сказал:

                Я со всем согласен. Однако хочу прийти все-таки к подтверждению/опровержению мысли: если есть списывающий документ БЕЗ проверок и БЕЗ установки блокировок (вроде ручной операции), то в минус списать может не только ручная операция, что понятно, НО и ПРАВИЛЬНЫЙ документ, где устнавливаются блокировки.

                • Евгений Гилев (Мастер-тренер) сказал:

                  Повторю – да, такая ситуация возможна.

                  Но посмотрите на ситуацию с другой стороны – есть документ с контролем остатка, есть документ без контроля остатка (ручная операция).
                  Мы проводим эти документы параллельно и получаем отрицательный остаток.

                  Разве нужно в этой проблеме винить документ с контролем остатка? Очевидно, что проблема в другом документе :)

  25. kalamber сказал:

    Добрый день! Если все типовые конфигурации на управляемых блокировках, тогда, если у объекта установлен режим управления автоматический, то блокировка все равно будет управляемой? СУБД не “наложит” автоматическую блокировку на объект?

    • Евгений Гилев (Мастер-тренер) сказал:

      Добрый день!

      Нет, автоматическая блокировка не будет наложена в этом случае.

      • kalamber сказал:

        Евгений, спасибо за ответ, но тогда возникает другой вопрос: почему в стандартных конфигурациях установлен управляемый режим, но у некоторых стандартных объектов установлен автоматический режим? Какой то когнитивный диссонанс…

        • Евгений Гилев (Мастер-тренер) сказал:

          В типовой бухгалтерии есть еще пара документов, где используется режим записи “Записывать модифицированные”, вместо “Записывать выбранные”.
          Есть ощущение, что это “торчят” обломки старых релизов/редакций :)

  26. Пётр сказал:

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

    • Пётр сказал:

      Не получается отредактировать пост… Конечно не для того, а перед тем как записать пустой набор :)

    • Евгений Гилев (Мастер-тренер) сказал:

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

  27. mrvaldem сказал:

    Добрый день.
    Подскажите, пожалуйста.
    Что бы не делать запись пустого набора в регистр не эффективнее ли: читать остатки и собственные движения и складывать их, т.о. получаем остатки за минусом (или плюсом, как кому надо) собственных движений. Этим экономим время и ресурсы на запись собственного пустого набора и удаления движений. Прочитать собственные движения для СКЛ не проблема, т.к. будет поиск по индексу (это регистратор).
    п.с. так делалось в УТ11 самых первых версий.

    • Евгений Гилев (Мастер-тренер) сказал:

      Добрый день!

      Поясню для сторонних читателей – вопрос про старую методику контроля остатков.

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

      Если же речь будет идти о себестоимости партий, то задача усложняется.

      И самое главное, что в новой методики такие пути оптимизации вообще не нужно придумывать (пример – текущая версия УТ 11).

  28. mrvaldem сказал:

    Добрый день.
    а не лучше ли блочить РН “свободные остатки” не перед записью, а перед проверкой на свободные остатки. Ведь это и есть самое “последнее” место. Вероятность того, что запрос будет не пустой (ошибка свободных остатков), мала, т.о. и время блокировки будет меньше, чем при записи в регистр.

    • Евгений Гилев (Мастер-тренер) сказал:

      Добрый день!

      Давайте смоделируем ситуацию, когда блокировка накладывается перед контролем остатков.

      Допустим остаток мониторов на складе 10 шт., 3 транзакции (Т1, Т2, Т3) параллельно начинают свою работу:
      1) Т1 списывает 8 шт.
      2) Т2 списывает 8 шт.
      2) Т3 списывает 8 шт.
      3) Т1 блокирует мониторы
      4) Т2 пытается заблокировать мониторы, попадает в состояние ожидания.
      4) Т3 пытается заблокировать мониторы, попадает в состояние ожидания.
      5) Т1 выполняет запрос проверки остатков
      6) Т1 выдает сообщение “Недостаточно 14 мониторов”
      7) Пользователь делает скриншот и пишет программисту “Ваша программа не работает – я хотел списать 8 шт, а мне сообщают, что не хватает 14 шт!”

      В общем с такой схемой будут проблемы :)

  29. mrvaldem сказал:

    Добрый день.
    В примечании Вы говорите о том, что не требуется накладывать блокировку на РН “Себестоимость”, т.к. блокировки для РН “Свободные остатки” достаточно. Но это если допустить, что в системе все документы двигают эти оба регистра одновременно. Но это врядли. Поэтому, наверное, блочить “Свободные остатки и если проверка на отрицательность прошла, то блочить “Себестоимость” и проводить.

    • Евгений Гилев (Мастер-тренер) сказал:

      Добрый день!

      Разумеется, могут быть случаи, когда нужно будет накладывать блокировки на регистр “Себестоимость товаров”.

      Но, например, в типовой УТ 11 блокировка на этот регистр не накладывается. Почему? Разработчики облегчили себе жизнь – работу с себестоимостью они вынести за рамки проведения документов, в регламентные операции закрытия месяца.

  30. dj_serega сказал:

    Как всегда на высоте! Большое спасибо.

    Не один раз делал расчет суммы списания по партиям. Но как-то не видел такого подхода :-))

  31. nikitoz сказал:

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

    • Евгений Гилев (Мастер-тренер) сказал:

      Разверните, пожалуйста, мысль про “наворот” :)

      • nikitoz сказал:

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

        ЗЫ: данные передаются через ДополнительныеСвойства.
        ЗЫЫ: не довелось применять в реальных конфигурациях.

        • Евгений Гилев (Мастер-тренер) сказал:

          Похожий подход используется в типовой УТ 11.
          Но это тема для отдельной статьи :)

          • nikitoz сказал:

            Да, нельзя объять необъятное)
            В любом случае в избранное для будущего копипаста)

            • Евгений Гилев (Мастер-тренер) сказал:

              При копипасте формулировок не забывайте про указание авторства ;)

  32. nik сказал:

    Добрый день! В требованиях к экзамену 1С:Специалист по платформе 8 среди типичных ошибок есть такая: “Если при проведении документа используются каким-то образом данные, считываемые из регистров, обязательно требуется предусмотреть получение таких данных на момент проведения документа”. Могу ошибаться, но, на мой взгляд, указание “Неопределено” в параметрах вирт. таблицы будет ошибкой и на экзамене будет снят 1 балл …

    • Евгений Гилев (Мастер-тренер) сказал:

      Добрый день!

      Вы говорите о ситуации оперативного проведения документа?
      Если документ проводится оперативно, это означает, что на этот момент он является последним на оси времени. Поэтому использование параметра Неопределено как раз является более правильным.

      Кстати, в типовой УТ 11, вообще не указывается параметр период, при получении остатка.

  33. Максим сказал:

    Зачем мы 2 раза тут записываем движения?
    // 3.1. Блокировка остатков регистра #Область Область3_1 Движения.СвободныеОстатки.БлокироватьДляИзменения = Истина; Движения.Записать(); #КонецОбласти
    // 4. Запись движений в БД #Область Область4 Движения.СвободныеОстатки.Записывать = Истина; Движения.Записать(); #КонецОбласти

    • Евгений Гилев (Мастер-тренер) сказал:

      По факту запись происходит в области 4, ведь именно там устанавливается флаг “Записывать” у набора записей.
      Холостая записать в области 3.1, это ошибка копипаста :)

  34. deaddy64 сказал:

    В коде для списания по FIFO итоги в запросе разве следует получать по Номенклатуре? В запросе же из “Блокировки в старой методике контроля остатков” итоги получают по НомеруСтроки. Или это опечатка?

    • Евгений Гилев (Мастер-тренер) сказал:

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

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

      • deaddy64 сказал:

        Упустил это из виду. Спасибо.

        Очень подробная и доходчивая статья. Спасибо за ваш труд.)

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Мы используем файлы cookies, чтобы сделать сайт удобнее.
Продолжая просмотр сайта, Вы соглашаетесь с их использованием.
Подробнее