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