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