Хотел бы поделиться своими соображениями по поводу использования Старого или Нового метода учета остатков при списании.
Структура для ОУ:
2 Регистра:
1. "Остатки товаров"
изм: Номенклатура, склад
рес: Количество
2. "Стоимость товаров"
изм: Номенклатура, Партия
Рес: Количество, Стоимость
(Из условия задачи, себестоимость не учитывается в разрезе складов т.е. Остатки списываем со склада а себестоимость по учетной политике, даже если склад получается разным)
Мой листинг модуля документа РасходнаяНакладная для ОУ:
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
Метод = РегистрыСведений.УчетнаяПолитика.ПолучитьПоследнее(Новый Граница(МоментВремени())).МетодСписания; // быстрее в 2 раза чем через запрос
Если ЗначениеЗаполнено(Метод) Тогда
Если Метод = Перечисления.МетодСписания.LIFO Тогда
Порядок = " Убыв";
Иначе
Порядок = "";
КонецЕсли;
Иначе
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Заполните Учетную политику!";
Сообщение.Сообщить();
Отказ = Истина;
Возврат;
КонецЕсли;
//- Получить учетную политику
//+ Получаем номенклатуру без услуг Делаем движения по отаткам
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
Запрос.Текст = "ВЫБРАТЬ
| РасходнаяНакладнаяТовар.Номенклатура,
| СУММА(РасходнаяНакладнаяТовар.Количество) КАК Количество
|ПОМЕСТИТЬ ДокТЧ
|ИЗ
| Документ.РасходнаяНакладная.Товар КАК РасходнаяНакладнаяТовар
|ГДЕ
| РасходнаяНакладнаяТовар.Ссылка = &Ссылка
| И НЕ РасходнаяНакладнаяТовар.Номенклатура.Услуга
|
|СГРУППИРОВАТЬ ПО
| РасходнаяНакладнаяТовар.Номенклатура,
| РасходнаяНакладнаяТовар.Ссылка.Склад
|
| ИНДЕКСИРОВАТЬ ПО
| Номенклатура
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ДокТЧ.Номенклатура,
| ДокТЧ.Количество КАК Количество
|ИЗ
| ДокТЧ КАК ДокТЧ";
Запрос.УстановитьПараметр("Ссылка", Ссылка);
РезультатЗапроса = Запрос.ВыполнитьПакет();
СписатьОстатки = РезультатЗапроса[1].Выбрать();
Движения.ОстаткиТоваров.Записывать = Истина;
Движения.ОстаткиТоваров.Очистить();
Пока СписатьОстатки.Следующий() Цикл
Движение = Движения.ОстаткиТоваров.ДобавитьРасход();
Движение.Период = Дата;
Движение.Склад = Склад;
Движение.Номенклатура = СписатьОстатки.Номенклатура;
Движение.Количество = СписатьОстатки.Количество;
КонецЦикла;
Движения.ОстаткиТоваров.БлокироватьДляИзменения = Истина;
Движения.Записать();
//- Получаем номенклатуру без услуг Делаем движения по отаткам
//+ Проверяем отриц остатки
Запрос.Текст = "ВЫБРАТЬ
| Остатки.Номенклатура.НаименованиеПолное КАК НаименованиеПолное,
| Остатки.КоличествоОстаток
|ИЗ
| РегистрНакопления.ОстаткиТоваров.Остатки(
| &ТочкаИтогов,
| Номенклатура В
| (ВЫБРАТЬ
| ДокТЧ.Номенклатура
| ИЗ
| ДокТЧ КАК ДокТЧ)
| И Склад = &Склад) КАК Остатки
|ГДЕ
| Остатки.КоличествоОстаток < 0
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ДокТЧ.Номенклатура
|ИЗ
| ДокТЧ КАК ДокТЧ";
Запрос.УстановитьПараметр("Склад", Склад);
Запрос.УстановитьПараметр("ТочкаИтогов", Новый Граница(МоментВремени(), ВидГраницы.Включая));
ПакетРезультатов = Запрос.ВыполнитьПакет();
РезультатЗапроса = ПакетРезультатов[0];
Если НЕ РезультатЗапроса.Пустой() Тогда
Отказ = Истина;
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Мало товара " + Выборка.НаименованиеПолное + " на складе - "+ Склад +" нужно еще " + (-Выборка.КоличествоОстаток);
Сообщение.Сообщить();
КонецЦикла;
КонецЕсли;
Если Отказ Тогда
Возврат;
КонецЕсли;
//- Проверяем отриц остатки
//+ Блокировка Создаем движения в регистре партий
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.СтоимостьТоваров");
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
ЭлементБлокировки.ИсточникДанных = ПакетРезультатов[1] ;
ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Номенклатура", "Номенклатура");
Блокировка.Заблокировать();
Если РежимПроведения = РежимПроведенияДокумента.Оперативный Тогда
Движения.СтоимостьТоваров.Очистить();
Движения.СтоимостьТоваров.БлокироватьДляИзменения = Истина;
движения.СтоимостьТоваров.Записать();
КонецЕсли;
Запрос.Текст = "ВЫБРАТЬ
| ДокТЧ.Номенклатура КАК Номенклатура,
| ДокТЧ.Количество КАК Количество,
| СтоимостьТоваров.Партия,
| СтоимостьТоваров.КоличествоОстаток,
| СтоимостьТоваров.СтоимостьОстаток
|ИЗ
| ДокТЧ КАК ДокТЧ
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.СтоимостьТоваров.Остатки(
| &ТочкаИтоговДляСебестоимости,
| Номенклатура В
| (ВЫБРАТЬ
| ДокТЧ.Номенклатура
| ИЗ
| ДокТЧ КАК ДокТЧ)) КАК СтоимостьТоваров
| ПО ДокТЧ.Номенклатура = СтоимостьТоваров.Номенклатура
|
|УПОРЯДОЧИТЬ ПО
| СтоимостьТоваров.Партия.МоментВремени " + Порядок +"
|ИТОГИ
| МИНИМУМ(Количество)
|ПО
| Номенклатура";
Запрос.УстановитьПараметр("ТочкаИтоговДляСебестоимости", МоментВремени());
Движения.СтоимостьТоваров.Очистить();
РезультатЗапроса = Запрос.Выполнить();
ВыборкаТовар = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаТовар.Следующий() Цикл
ОсталосьСписать = ВыборкаТовар.Количество;
ВыборкаПартия = ВыборкаТовар.Выбрать();
Пока ВыборкаПартия.Следующий() И ОсталосьСписать <> 0 Цикл
Списать = МИН(ОсталосьСписать, ВыборкаПартия.КоличествоОстаток);
Движение = Движения.СтоимостьТоваров.ДобавитьРасход();
Движение.Период = Дата;
Движение.Номенклатура = ВыборкаПартия.Номенклатура;
Движение.Партия = ВыборкаПартия.Партия;
Движение.Количество = Списать;
Движение.Стоимость = Списать / ВыборкаПартия.КоличествоОстаток * ВыборкаПартия.СтоимостьОстаток;
ОсталосьСписать = ОсталосьСписать - Списать;
КонецЦикла;
КонецЦикла;
Движения.СтоимостьТоваров.Записывать = Истина;
//- Блокировка Создаем движения в регистре партий
КонецПроцедуры
Короткий план:
1. Получаем учетную политику на тек момент.
2. Запросом получаем список номенклатуры без услуг. А так же сразу же создаем индексированную временную таблицу для последующих запросов. Делаем движения по остаткам товаров.
3. Получаем из Регистра Остатки товаров записи с отрицательными остатками, если записи есть - отмена транзакции иначе продолжаем
4. Блокируем регистр Стоимость товаров по номенклатуре. Читаем регистр по учет политике, делаем движения, записываемся.
Теперь нюансы:
1. В этой задаче обязателен запрос на выборку номенклатуры без услуг.
Обращение через точку в цикле - ошибка! При получении значения через точку мы не только получаем нужное значение но и все другие реквизиты.
2. По совету коллег – не использовать метод "Сообщить("")" т.к. он плохо работает в Web интерфейсе, использовать "СообщениеПользователю"
3. А теперь самое интересное, почему новая методика а не старая.
За образец "Старого" решения я взял решение от Alex1248, убрал в нем все движения/блокировки, все что относится к бухгалтерским задачам и начал замеры производительности.
Результаты:
Производительность моего решения показала лучший результат при 600+ записей в Табличной части документа (600+ одинаковых номенклатур в ТЧ). Что дает нам понять, при росте заполнености базы мое решение будет работать в разы быстрее. (При 1000 записей в ТЧ производительность различалась на 40% в мою пользу).
Как итог: Запись происходит быстрее, блокировки короче.
Код Alex1248 с которым сравнивал:
Процедура ОбработкаПроведения(Отказ, Режим)
Метод = РегистрыСведений.УчетнаяПолитика.ПолучитьПоследнее(Дата).Метод;
Если ЗначениеЗаполнено(Метод) Тогда
Если Метод = Перечисления.УчетнаяПолитика.ЛИФО Тогда
Порядок = " Убыв";
Иначе
Порядок = "";
КонецЕсли;
Иначе
Сообщить("Заполните метод списания!");
Отказ = Истина;
Возврат;
КонецЕсли;
// регистр ОстаткиНоменклатуры Расход
Движения.ОстаткиНоменклатуры.Записывать = Истина;
// регистр Себестоимость Расход
Движения.Себестоимость.Записывать = Истина;
// регистр бухгалтерии
//Движения.РегистрБухгалтерии.Записывать = Истина;
Блокировка = Новый БлокировкаДанных;
ЭлБлокировки = Блокировка.Добавить("РегистрНакопления.Себестоимость");
ЭлБлокировки.ИсточникДанных = СписокНоменклатуры;
ЭлБлокировки.ИспользоватьИзИсточникаДанных("Номенклатура", "Номенклатура");
ЭлБлокировки = Блокировка.Добавить("РегистрНакопления.ОстаткиНоменклатуры");
ЭлБлокировки.ИсточникДанных = СписокНоменклатуры;
ЭлБлокировки.ИспользоватьИзИсточникаДанных("Номенклатура", "Номенклатура");
ЭлБлокировки.УстановитьЗначение("Склад", Склад);
Блокировка.Заблокировать();
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
| РасходнаяНакладнаяСписокНоменклатуры.Номенклатура,
| РасходнаяНакладнаяСписокНоменклатуры.Ссылка.Склад,
| СУММА(РасходнаяНакладнаяСписокНоменклатуры.Количество) КАК Количество,
| СУММА(РасходнаяНакладнаяСписокНоменклатуры.Сумма) КАК Сумма
|ПОМЕСТИТЬ ТчДок
|ИЗ
| Документ.РасходнаяНакладная.СписокНоменклатуры КАК РасходнаяНакладнаяСписокНоменклатуры
|ГДЕ
| РасходнаяНакладнаяСписокНоменклатуры.Ссылка = &Ссылка
| И РасходнаяНакладнаяСписокНоменклатуры.Номенклатура.Услуга = ЛОЖЬ
|
|СГРУППИРОВАТЬ ПО
| РасходнаяНакладнаяСписокНоменклатуры.Номенклатура,
| РасходнаяНакладнаяСписокНоменклатуры.Ссылка.Склад
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ТчДок.Склад,
| ТчДок.Номенклатура КАК Номенклатура,
| СебестоимостьОстатки.Партия,
| ТчДок.Количество КАК КолДок,
| ЕСТЬNULL(СебестоимостьОстатки.КолвоОстаток, 0) КАК КолОст,
| ЕСТЬNULL(СебестоимостьОстатки.СебестоимостьОстаток, 0) КАК СумОст,
| ЕСТЬNULL(ОстаткиНоменклатурыОстатки.КоличествоОстаток, 0) КАК КолСклад
|ИЗ
| ТчДок КАК ТчДок
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ОстаткиНоменклатуры.Остатки(
| &Момент,
| (Склад, Номенклатура) В
| (ВЫБРАТЬ
| ТчДок.Склад,
| ТчДок.Номенклатура
| ИЗ
| ТчДок КАК ТчДок)) КАК ОстаткиНоменклатурыОстатки
| ПО ТчДок.Номенклатура = ОстаткиНоменклатурыОстатки.Номенклатура
| И ТчДок.Склад = ОстаткиНоменклатурыОстатки.Склад
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.Себестоимость.Остатки(
| &Момент,
| Номенклатура В
| (ВЫБРАТЬ
| ТчДок.Номенклатура
| ИЗ
| ТчДок КАК ТчДок)) КАК СебестоимостьОстатки
| ПО ТчДок.Номенклатура = СебестоимостьОстатки.Номенклатура
|
|УПОРЯДОЧИТЬ ПО
| СебестоимостьОстатки.Партия.МоментВремени" + Порядок + "
|ИТОГИ
| МАКСИМУМ(КолДок)
|ПО
| Номенклатура";
Запрос.УстановитьПараметр("Ссылка", Ссылка);
Запрос.УстановитьПараметр("Момент", МоментВремени());
//Движения по регистрам накопления
Пакет = Запрос.ВыполнитьПакет();
Результат = Пакет[1].Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока Результат.Следующий() Цикл
Выборка = Результат.Выбрать();
Остаток = Результат.КолДок;
Пока Выборка.Следующий() И Остаток > 0 Цикл
Если Выборка.КолДок > Выборка.КолСклад Тогда
Сообщить("Товара не хывтает");
Отказ = Истина;
Возврат;
КонецЕсли;
//Остатки
Движение = Движения.ОстаткиНоменклатуры.Добавить();
Движение.ВидДвижения = ВидДвиженияНакопления.Расход;
Движение.Период = Дата;
Движение.Номенклатура = Выборка.Номенклатура;
Движение.Склад = Склад;
Движение.Количество = мин(Выборка.КолОст,Остаток);
//Себестоимость
Движение = Движения.Себестоимость.Добавить();
Движение.ВидДвижения = ВидДвиженияНакопления.Расход;
Движение.Период = Дата;
Движение.Номенклатура = Выборка.Номенклатура;
Движение.Партия = Выборка.Партия;
Движение.Колво = мин(Выборка.КолОст,Остаток);
Движение.Себестоимость = ?(Выборка.КолОст = 0,0, Движение.Колво * Выборка.СумОст / Выборка.КолОст);
Остаток = Остаток - Движение.Колво;
КонецЦикла;
КонецЦикла;
КонецПроцедуры
Кстати
РегистрыСведений.УчетнаяПолитика.ПолучитьПоследнее(Новый Граница(МоментВремени())).МетодСписания;
Работает быстрее чем через запрос в 2 раза. но разница в 0.0015 секунды (0.0030 против 0.0015).
Готов услышать критику и замечания.