Бывало ли у Вас такое: зарезервировали товар по телефону, собрались проводить документ, а товара уже нет в доступном остатке на складе? Как же так? Что ж такое? Недавно был виден остаток в подборе. Приходится вычеркивать товар либо брать партию по другой цене или с неподходящими для покупателя параметрами. Приходится извиняться либо вообще перезванивать и согласовывать новые условия заказа. А дорога каждая минута.
Все очень просто. Пока Вы разговаривали, кто-то успел провести документ, который зарезервировал товар. Или отработал робот, который грузит заявки, поступившие с сайта.
Что делать? Есть одна идея. Резервировать товар в не проведенных документах во временных резервах. Более того резервировать товар в новых, еще незаписанных документах. И даже изменять резерв при редактировании количества в строке заявки, удалении строки, изменении склада и других действиях в записанных и новых документах. Резерв сразу увидят все пользователи.
Можно предусмотреть также аварийное завершение программы у пользователя, когда его документы не сохранились и временные резервы должны быть удалены.
Что нужно сделать? Рассмотрим работу с форматом SQL. Все это писалось в 2007 году, но до сих пор работает.
Необходимо создать свою таблицу 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. Создавайте приходный документ и далее балуйтесь с заявкой.
Вот вкратце и все. Если тема показалась интересной - пишите, будем внедрять.