Бывало ли у Вас такое: зарезервировали товар по телефону, собрались проводить документ, а товара уже нет в доступном остатке на складе? Как же так? Что ж такое? Недавно был виден остаток в подборе. Приходится вычеркивать товар либо брать партию по другой цене или с неподходящими для покупателя параметрами. Приходится извиняться либо вообще перезванивать и согласовывать новые условия заказа. А дорога каждая минута.

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

Что делать? Есть одна идея. Резервировать товар в не проведенных документах во временных резервах. Более того резервировать товар в новых, еще незаписанных документах. И даже изменять резерв при редактировании количества в строке заявки, удалении строки, изменении склада и других действиях в записанных и новых документах. Резерв сразу увидят все пользователи.

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

Что нужно сделать? Рассмотрим работу с форматом SQL. Все это писалось в 2007 году, но до сих пор работает. smiley

Необходимо  создать свою таблицу SQL примерно такой структуры: Товар, Склад, Резерв, IDDOC. Если склад адресный - добавляется поле для ячейки.

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

Функция глПолучитьСвободныйОстаток(ВыбТовар, ВыбСклад, ПоложительныеОстатки = 0, ОстаткиВТЗ = 0) Экспорт
    ТекстЗапроса = "
    |SELECT SUM(Запрос.Остаток - Запрос.Резерв) AS Количество
    |FROM
    |(
    |SELECT ОстаткиТМЦОстатки.КоличествоОстаток Остаток
    |    , 0 Резерв
    |FROM $РегистрОстатки.ОстаткиТМЦ(,,
    |        (Номенклатура = :ВыбТовар)
    |        AND (Склад = :ВыбСклад)
    |        ,,
    |        Количество) AS ОстаткиТМЦОстатки
    |
    |UNION ALL
    |
    |SELECT 0 Остаток
    |, РезервыТМЦОстатки.КоличествоОстаток Резерв
    |FROM $РегистрОстатки.РезервыТМЦ(,,
    |        (Номенклатура = :ВыбТовар)
    |        AND (Склад = :ВыбСклад)
    |        ,,
    |        Количество) AS РезервыТМЦОстатки
    |
    |UNION ALL
    |
    |SELECT 0 Остаток
    |, ВременныйРезерв.Res Резерв
    |FROM Tempost AS ВременныйРезерв (NOLOCK)
    |WHERE ВременныйРезерв.Tov = :ВыбТовар
    |      AND ВременныйРезерв.Skl = :ВыбСклад
    |) AS Запрос
    |";
        
    Если ПоложительныеОстатки = 1 Тогда //только остатки > 0
        ТекстЗапроса = ТекстЗапроса + "
        |HAVING SUM(Запрос.Остаток - Запрос.Резерв) > 0
        |";
    КонецЕсли;
    
    RecordSet.УстановитьТекстовыйПараметр("ВыбТовар",    ВыбТовар);
    RecordSet.УстановитьТекстовыйПараметр("ВыбСклад",    ВыбСклад);
        
    ТЗ = RecordSet.ВыполнитьИнструкцию(ТекстЗапроса);
    
    Если ОстаткиВТЗ = 0 Тогда
        Возврат ТЗ.ПолучитьЗначение(1,1);
    Иначе    
        Возврат ТЗ;
    КонецЕсли;    
КонецФункции

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

Процедура ПриОтменеПроведенияДокумента(Док)
    //Переводим на всякий случай во временный резерв
    //Нужно следить за тем чтобы непроведенные документы долго не болтались
    Если Док.Вид() = "ЗаявкаПокупателя" Тогда
        ТекстЗапроса = "
        |INSERT INTO Tempost
        |SELECT $РезервыТМЦ.Номенклатура
        |, $РезервыТМЦ.Склад
        |, $РезервыТМЦ.Количество
        |, РезервыТМЦ.IDDOC
        |FROM $Регистр.РезервыТМЦ AS РезервыТМЦ
        |WHERE (РезервыТМЦ.IDDOC = :ТекДок)
        |";
        
        RecordSet.УстановитьТекстовыйПараметр("ТекДок", Док.ТекущийДокумент());
        RecordSet.ВыполнитьИнструкцию(ТекстЗапроса);        
    КонецЕсли;    
КонецПроцедуры

Процедура ПриУдаленииДокумента(Док, Реж)
    //Удаляем временный резерв по документу
    Если Док.Вид() = "ЗаявкаПокупателя" Тогда
        ТекстЗапроса = "
        |DELETE
        |FROM Tempost (HOLDLOCK)
        |WHERE IDDOC = :ВыбДок
        |";
        
        RecordSet.УстановитьТекстовыйПараметр("ВыбДок", Док);
        
        RecordSet.ВыполнитьИнструкцию(ТекстЗапроса);
    КонецЕсли;
КонецПроцедуры

Также создаем функцию глИзменитьРезерв(), которая будет анализировать свободный остаток и сравнивать его с количеством товара в документе (при этом для проведенных документов будем учитывать не только содержимое временных резервов, но и движения документа). При возможности зарезервировать товар она будет записывать резерв во временное хранилище. Фактически каждое редактирование содержимого документа будет добавлять записи со знаком - или + в таблицу временных резервов. Вот фрагмент текста функции:

Если ПоДвижениямДокумента = 1 Тогда
    ТекстЗапроса = "
    |SELECT Sum(ISNULL($РезервыТМЦ.Количество,0) + ISNULL(ВременныйРезерв.Res,0)) Количество
    |FROM $Регистр.РезервыТМЦ AS РезервыТМЦ (NOLOCK)
    |LEFT JOIN Tempost AS ВременныйРезерв (NOLOCK) ON РезервыТМЦ.IDDOC = ВременныйРезерв.IDDOC
    |AND $РезервыТМЦ.Номенклатура    = ВременныйРезерв.Tov
    |AND $РезервыТМЦ.Склад           = ВременныйРезерв.Skl
    |WHERE (РезервыТМЦ.IDDOC            = :ТекДок)
    |    AND ($РезервыТМЦ.Номенклатура  = :Товар)
    |    AND ($РезервыТМЦ.Склад         = :Склад)
    |";

    RecordSet.УстановитьТекстовыйПараметр("ТекДок",   ТекДок);
    RecordSet.УстановитьТекстовыйПараметр("Товар",    Товар);
    RecordSet.УстановитьТекстовыйПараметр("Склад",    Склад);

    ТаблИтогов = RecordSet.ВыполнитьИнструкцию(ТекстЗапроса);
Иначе
    ВыдаватьСообщение = 0; //документ может быть старым и во временных резервах отсутствовать

    ТекстЗапроса = "
    |SELECT Sum(ВременныйРезерв.Res) Количество
    |FROM Tempost AS ВременныйРезерв
    |WHERE (ВременныйРезерв.IDDOC    = :ИДДок)
    |    AND (ВременныйРезерв.Tov    = :Товар)
    |    AND (ВременныйРезерв.Skl    = :Склад)
    |";

    RecordSet.УстановитьТекстовыйПараметр("ИДДок",    ИДДок);
    RecordSet.УстановитьТекстовыйПараметр("Товар",    Товар);
    RecordSet.УстановитьТекстовыйПараметр("Склад",    Склад);

    ТаблИтогов = RecordSet.ВыполнитьИнструкцию(ТекстЗапроса);
КонецЕсли;    

Далее модифицируем модуль формы документа "Заявка покупателя". В процедуру ПриОткрытии() добавляем код создания временной таблицы хранения резервов (для отката изменений):

Процедура ПриОткрытии()
    СтарыйСклад = Склад;
    
    ИДДок    = Meta.ЗначениеВСтрокуБД(ТекущийДокумент());
    ИДПольз  = Meta.ЗначениеВСтрокуБД(глПользователь);
    Если Выбран() = 0 Тогда
        ВТ = "temp" + СокрЛП(ИДПольз);
    Иначе
        ВТ = "temp" + СокрЛП(ИДДок);
    КонецЕсли;
    
    //для отката изменений
    ТекстЗапроса = "
    |if exists (select name from dbo.sysobjects where name = '" + ВТ + "' and xtype = 'U ')
    |drop table " + ВТ + "
    |";
    
    RecordSet.ВыполнитьИнструкцию(ТекстЗапроса);
    
    RecordSet.ВыполнитьИнструкцию("create table " + ВТ 
                                  + " (tov char(9), skl char(9), res numeric (15,3), iddoc char(9))");
        
    RecordSet.УстановитьТекстовыйПараметр("ИДДок", ИДДок);
    RecordSet.ВыполнитьИнструкцию("insert into " + ВТ + " select * from tempost where iddoc = :ИДДок");
КонецПроцедуры

При записи документа производим хитрые манипуляции с таблицей для отката изменений и изменяем Iddoc в таблице временных резервов:

Процедура ПриЗаписи()
    Если Выбран() = 1 Тогда
        Возврат;
    КонецЕсли;    
    
    Записать();
    
    СтарыйИДДок = Лев(Meta.ЗначениеВСтрокуБД(глПользователь), 6) + "ZZZ";
    ИДДок       = Meta.ЗначениеВСтрокуБД(ТекущийДокумент());
    
    //назначаем постоянный иддок новому документу
    RecordSet.УстановитьТекстовыйПараметр("ИДДок",         ИДДок);
    RecordSet.УстановитьТекстовыйПараметр("СтарыйИДДок", СтарыйИДДок);
    RecordSet.ВыполнитьИнструкцию("update tempost (HOLDLOCK) set iddoc = :ИДДок where iddoc = :СтарыйИДДок");
    
    ИДДок    = Meta.ЗначениеВСтрокуБД(ТекущийДокумент());
    ИДПольз  = Meta.ЗначениеВСтрокуБД(глПользователь);
    
    //таблицу для отката переименуем
    ВТ       = "temp" + СокрЛП(ИДПольз);
    ВТНов    = "temp" + СокрЛП(ИДДок);
    
    RecordSet.ВыполнитьИнструкцию("EXEC sp_rename '" + ВТ + "', '" + ВТНов + "'");
    
    //если документ записан - удалим его из таблицы открытых документов
    RecordSet.УстановитьТекстовыйПараметр("ИДДок", ИДДок);
    RecordSet.ВыполнитьИнструкцию("delete from openz with(HOLDLOCK) where iddoc = :ИДДок");
КонецПроцедуры

Процедура ПриЗакрытии() не менее хитрая, нам нужно откатить изменения:

Процедура ПриЗакрытии()
    Если Выбран() = 1 Тогда
        //если документ не записывается но изменялся - возвращаем назад состояние таблицы
        ИДДок    = Meta.ЗначениеВСтрокуБД(ТекущийДокумент());
        ВТ       = "temp" + СокрЛП(ИДДок);
        
        Если Модифицированность() = 1 Тогда
            RecordSet.УстановитьТекстовыйПараметр("ИДДок", ИДДок);
            RecordSet.ВыполнитьИнструкцию("delete from tempost with(HOLDLOCK) where iddoc = :ИДДок");
            
            RecordSet.ВыполнитьИнструкцию("insert into tempost with(HOLDLOCK) select * from " + ВТ);
            
            //если документ не изменялся сознательно - удалим его из таблицы открытых документов
            RecordSet.УстановитьТекстовыйПараметр("ИДДок", ИДДок);
            RecordSet.ВыполнитьИнструкцию("delete from openz with(HOLDLOCK) where iddoc = :ИДДок");
        КонецЕсли;
        
        RecordSet.ВыполнитьИнструкцию("drop table " + ВТ);
        Возврат;
    КонецЕсли;
    
    //если новый документ не записывается - очищаем таблицу
    ИДДок = Лев(Meta.ЗначениеВСтрокуБД(глПользователь), 6) + "ZZZ";
    
    ИДПольз   = Meta.ЗначениеВСтрокуБД(глПользователь);
    ВТ        = "temp" + СокрЛП(ИДПольз);
    
    RecordSet.УстановитьТекстовыйПараметр("ИДДок", ИДДок);
    RecordSet.ВыполнитьИнструкцию("delete from tempost with(HOLDLOCK) where iddoc = :ИДДок");
    
    RecordSet.ВыполнитьИнструкцию("drop table " + ВТ);
КонецПроцедуры        

Самое сложное позади. Осталось предусмотреть редактирование данных документа. Приведу коды некоторых процедур:

Процедура ПриУдаленииСтроки()
    глИзменитьРезерв(ТекущийДокумент(), Номенклатура, Склад, 0, -Количество);
КонецПроцедуры

Процедура ПриНачалеРедактированияСтроки()
    СтароеКоличество = Количество;
КонецПроцедуры

Процедура ПриОкончанииРедактированияСтроки()
    МаксКолво = 0;
    Если глИзменитьРезерв(ТекущийДокумент(), Номенклатура, Склад, Количество, 
                          Количество - СтароеКоличество, МаксКолво) = 0 Тогда
        Количество = МаксКолво;
    КонецЕсли;    
КонецПроцедуры

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

  • Товар, который подбирается в заявку, будет в свободном остатке и заявка однозначно будет проведена (не придется звонить и извиняться перед заказчиком)
  • Ускорение проведения документов
  • Ускорение вывода доступных остатков в форме подбора, отчетах, формах документов

Можете скачать простую демо конфигурацию для SQL. Создавайте приходный документ и далее балуйтесь с заявкой.

Демо конфигурация online резервирования (только для SQL)

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

Другие статьи по прямым запросам:

Ускоряем регистрацию объектов в МОД (для SQL)

Проверка дублей строк с помощью 1С++

Аналог ON DUPLICATE KEY UPDATE в MS SQL

Как использовать УРБД в отличающихся конфигурациях

Примеры решения нестандартных задач на T-SQL в 1С

Как написать прямой запрос в 1С (DBF, 1sqlite)

Запросы в 1С к двум базам одновременно (DBF, OLE DB)

Как написать прямой запрос в 1С (SQL) с помощью 1С++