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