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