Как получить последний номер документа в 1С, учитывая создание задним числом и изменяющиеся префиксы?

Программист 1С v8.3 (Управляемые формы) Управленческий учет Промышленность, строительство и АПК
← На главную

При работе с системой 1С:Предприятие часто возникает задача программного получения последнего номера документа. Эта задача кажется простой на первый взгляд, но усложняется, когда документы могут быть созданы "задним числом", а также когда система нумерации документов использует динамически изменяющиеся префиксы. Мы сталкиваемся с ситуацией, где стандартные подходы к определению "последнего" документа оказываются ненадежными. Давайте подробно разберем, как эффективно решить эту проблему, используя как механизмы платформы, так и более сложные запросные решения.

Почему стандартные методы получения номера могут не работать?

Когда мы пытаемся получить последний номер документа, ориентируясь на его дату, мы можем столкнуться с неточностями. Проблема возникает, когда пользователи создают документы не в хронологическом порядке, то есть "задним числом". Например, документ с датой вчерашнего дня может быть фактически создан сегодня после документа с сегодняшней датой. В такой ситуации простой запрос с сортировкой по дате в убывающем порядке даст неверный результат.

Другая сложность связана с использованием префиксов в номерах документов. Многие организации используют префиксы для разделения нумерации по организациям, подразделениям или типам операций (например, "ОРГ1-000001", "ОРГ2-000005", "ОРГ1-П-000003"). Если префиксы имеют разную длину или меняются со временем, простой поиск максимального значения номера (МАКСИМУМ(Номер)) может привести к ошибкам, так как строковое сравнение номеров не всегда соответствует их числовому значению. Например, "А-100" может быть меньше "Б-10", если сравнивать строки. При решении этой проблемы в запросах со строками разной длины может пригодиться алгоритм для определения длины строки прямо в запросе 1С.

Кроме того, в случае, когда нумерация документа зависит от сложной логики (например, наличия документа-основания, как в рассматриваемом случае с добавлением буквы "П" в номер), стандартная автонумерация без дополнительной настройки может не справиться, и нам потребуется вмешательство в процесс формирования номера. Для точного понимания того, в какой момент жизненного цикла объекта лучше внедрить свой программный код, можно использовать инструмент для интерактивного изучения последовательности вызова событий в 1С.

Рекомендуемый подход: использование стандартных механизмов платформы 1С

Наиболее правильным и надежным с точки зрения архитектуры 1С является использование встроенных механизмов платформы для управления нумерацией документов. Эти механизмы специально разработаны для обеспечения уникальности, последовательности и корректности нумерации, даже в сложных сценариях. Использование запросов для ежедневного получения последнего номера часто является антипаттерном, поскольку может приводить к ошибкам конкурентного доступа и неэффективности.

Рассмотрим основные подходы:

  1. Настройка автонумерации и периодичности.

    Для каждого типа документа в 1С можно настроить автонумерацию. Система будет автоматически отслеживать последний присвоенный номер и корректно увеличивать его при создании нового документа. Мы можем установить периодичность номера, например, "В пределах года" или "В пределах месяца". Если выбрать "В пределах года", нумерация будет автоматически начинаться с "1" в каждом новом году, что решает проблему сброса нумерации без дополнительных условий в запросах. Это существенно упрощает поддержку и повышает надежность системы.

  2. Событие ПриУстановкеНовогоНомера.

    Это наиболее гибкий механизм для реализации сложной логики нумерации. Мы можем создать обработчик для события ПриУстановкеНовогоНомера объекта документа. В этом обработчике мы можем написать код, который будет анализировать любые необходимые условия (например, наличие документа-основания, тип операции, организацию) и формировать нужный номер с соответствующим префиксом. Платформа сама вызовет этот обработчик в момент присвоения номера, обеспечив его уникальность и последовательность. Это позволяет полностью контролировать процесс нумерации, не прибегая к ручному поиску последнего номера.

    Предположим, у нас есть документ "ЗаказНаПроизводство2_2", и нам нужно добавить букву "П" в номер, если нет документа-основания. Мы можем реализовать это следующим образом в обработчике события ПриУстановкеНовогоНомера для данного документа:

    
    Процедура ЗаказНаПроизводство2_2ПриУстановкеНовогоНомера(СтандартнаяОбработка, Префикс)
    
        // Отменяем стандартную обработку, чтобы полностью контролировать формирование номера
        СтандартнаяОбработка = Ложь;
    
        Перем НовыйПрефикс;
        Перем КоличествоНомеров;
    
        // Определяем префикс в зависимости от наличия документа-основания
        Если Объект.ДокументОснование.Пустая() Тогда
            // Если нет документа-основания, добавляем "П"
            НовыйПрефикс = Префикс + "П-"; 
        Иначе
            // Если есть документ-основание, используем стандартный префикс
            НовыйПрефикс = Префикс; 
        КонецЕсли;
    
        // Устанавливаем новый номер, используя определенный префикс
        // Параметры:
        //  Строка (тип номера) - обычно ИмяДокумента
        //  Строка (префикс) - наш сформированный префикс
        //  Дата (дата документа) - для учета периодичности
        //  Ссылка (объект) - ссылка на сам документ
        //  Число (количество символов в числовой части) - если нужно
        //  Строка (ДополнительныйПрефикс) - можно использовать для более сложной логики
        //  ПараметрДляУстановкиНовогоНомера (объект-владелец) - если нумерация подчинена
        Объект.УстановитьНовыйНомер(НовыйПрефикс, Объект.Дата, Объект.Ссылка); 
    
        // Дополнительная логика, если номер нужно доработать после установки
        // Например, если хотим обрезать или добавить что-то после установки
        // Это место, где можно внести корректировки, если базовая УстановитьНовыйНомер() 
        // не дает желаемый формат, но обычно она достаточно гибка.
    
    КонецПроцедуры
    

    Важно помнить, что метод УстановитьНовыйНомер() обычно вызывается из платформы при интерактивном создании нового документа. Мы также можем вызвать его при программном создании документа.

  3. Метод УстановитьНовыйНомер().

    Мы можем программно вызвать метод УстановитьНовыйНомер() для объекта документа, чтобы присвоить ему следующий свободный номер в соответствии с настроенной автонумерацией. Это полезно, когда нам нужно установить номер не интерактивно, а в результате какой-либо обработки. Перед вызовом этого метода мы можем предварительно установить необходимый префикс для документа, если это требуется по нашей бизнес-логике.

    
    НовыйЗаказ = Документы.ЗаказНаПроизводство2_2.СоздатьДокумент();
    НовыйЗаказ.Дата = ТекущаяДата();
    // Допустим, мы определили, что префикс должен быть "ОРГ-П-"
    Если НетДокументаОснования Тогда
        НовыйЗаказ.УстановитьНовыйНомер("ОРГ-П-", НовыйЗаказ.Дата, НовыйЗаказ.Ссылка);
    Иначе
        НовыйЗаказ.УстановитьНовыйНомер("ОРГ-", НовыйЗаказ.Дата, НовыйЗаказ.Ссылка);
    КонецЕсли;
    НовыйЗаказ.Записать();
    

Альтернативные методы: запросы для получения последнего номера (с ограничениями)

Хотя мы настоятельно рекомендуем использовать встроенные механизмы 1С для нумерации, бывают ситуации (например, для разовых обработок, анализа данных или миграции), когда требуется получить последний номер с помощью запроса. В таких случаях мы должны быть осведомлены об ограничениях и возможных проблемах.

  1. Простой запрос с сортировкой по дате:

    Изначальный запрос, который мы рассматриваем, выглядит так:

    
    ВЫБРАТЬ ПЕРВЫЕ 1
        ЗаказНаПроизводство2_2.Номер КАК Номер
    ИЗ
        Документ.ЗаказНаПроизводство2_2 КАК ЗаказНаПроизводство2_2
    ГДЕ
        ЗаказНаПроизводство2_2.ДокументОснование = Неопределено
    УПОРЯДОЧИТЬ ПО
        ЗаказНаПроизводство2_2.Дата УБЫВ
    

    Этот запрос находит документ с самой "поздней" датой. Проблема: как мы уже обсуждали, если документы создаются "задним числом", то есть документ с более ранней датой фактически создан позже, этот запрос вернет неверный "последний" документ. Он будет последним по дате, но не последним по факту создания.

  2. Использование МАКСИМУМ(Номер):

    Мы могли бы попытаться использовать функцию МАКСИМУМ для получения наибольшего номера:

    
    ВЫБРАТЬ
        МАКСИМУМ(ЗаказНаПроизводство2_2.Номер) КАК МаксимальныйНомер
    ИЗ
        Документ.ЗаказНаПроизводство2_2 КАК ЗаказНаПроизводство2_2
    ГДЕ
        ЗаказНаПроизводство2_2.ДокументОснование = Неопределено
    

    Проблема: Этот подход также не работает надежно при наличии префиксов. Функция МАКСИМУМ сравнивает строки, и результат строкового сравнения может отличаться от ожидаемого числового порядка. Например, "А-10" будет считаться "больше", чем "А-9", но "А-100" будет "меньше", чем "А-9", при обычном строковом сравнении. Более того, если префиксы менялись (например, "ОРГ1-" и "ОРГ2-П-"), то МАКСИМУМ выберет максимальный номер по алфавиту, а не по фактическому числовому значению.

    Мы выяснили, что из-за частой смены префиксов в течение последних месяцев, такой подход выдает неверные результаты.

  3. Использование МоментВремени для сортировки:

    Поле МоментВремени в 1С является более надежным способом для определения хронологического порядка создания или проведения документов, чем просто Дата. МоментВремени включает в себя не только дату, но и уникальный идентификатор транзакции, что позволяет точно определить последовательность событий, даже если они произошли в одну и ту же секунду.

    Пример запроса с МоментВремени:

    
    ВЫБРАТЬ ПЕРВЫЕ 1
        ЗаказНаПроизводство2_2.Номер КАК Номер
    ИЗ
        Документ.ЗаказНаПроизводство2_2 КАК ЗаказНаПроизводство2_2
    ГДЕ
        ЗаказНаПроизводство2_2.ДокументОснование = Неопределено
    УПОРЯДОЧИТЬ ПО
        ЗаказНаПроизводство2_2.МоментВремени УБЫВ
    

    Хотя МоментВремени является более точным, чем Дата, для определения последнего созданного документа, сам автор сообщает, что в его случае "МоментВремени не помогает". Это может быть связано с особенностями хранения или обработки данных, или с тем, что проблема не только в хронологии, но и в самих префиксах и числовых частях номеров.

  4. Извлечение числовой части номера с помощью ПОДСТРОКА:

    Когда префиксы мешают, мы можем использовать функцию ПОДСТРОКА языка запросов 1С для "вырезания" числовой части номера. Это позволяет сравнивать номера как числа, а не как строки. Для этого нам важно, чтобы числовая часть имела фиксированную длину (например, дополненная лидирующими нулями).

    Пример запроса, предложенный в обсуждении:

    
    ВЫБРАТЬ ПЕРВЫЕ 1
        ПОДСТРОКА(ЗаказНаПроизводство2_2.Номер, 3, 11) КАК НомерБезПрефикса,
        ГОД(ЗаказНаПроизводство2_2.Дата) КАК ГодДокумента
    ИЗ
        Документ.ЗаказНаПроизводство2_2 КАК ЗаказНаПроизводство2_2
    ГДЕ
        ЗаказНаПроизводство2_2.ДокументОснование = Неопределено
    УПОРЯДОЧИТЬ ПО
        ГодДокумента УБЫВ,
        НомерБезПрефикса УБЫВ
    

    Здесь мы предполагаем, что префикс занимает первые 2 символа, а числовая часть номера начинается с 3-го символа и имеет длину 11. Критически важно: Чтобы этот подход работал, все числовые части номеров после префикса должны быть одной длины (например, "00000000001", "00000000010"). Если длина числовой части разная, придется использовать более сложную логику, например, регулярные выражения или итеративный поиск первого нечислового символа, что значительно усложняет запрос. Мы рекомендуем навести порядок в нумерации, чтобы числовая часть всегда была фиксированной длины.

  5. Добавление фильтрации по году:

    Для того чтобы нумерация обнулялась каждый год (или чтобы мы получали последний номер только за текущий год), нам необходимо добавить условие по году в запрос. Это предотвратит получение номеров из прошлых лет, если в текущем году документы еще не создавались, или если мы ищем максимальный номер для конкретного года.

    Пример с учетом года:

    
    ВЫБРАТЬ ПЕРВЫЕ 1
        ПОДСТРОКА(ЗаказНаПроизводство2_2.Номер, 3, 11) КАК НомерБезПрефикса,
        ГОД(ЗаказНаПроизводство2_2.Дата) КАК ГодДокумента
    ИЗ
        Документ.ЗаказНаПроизводство2_2 КАК ЗаказНаПроизводство2_2
    ГДЕ
        ЗаказНаПроизводство2_2.ДокументОснование = Неопределено
        И ГОД(ЗаказНаПроизводство2_2.Дата) = ГОД(&ТекущаяДата) // Добавляем условие по году
    УПОРЯДОЧИТЬ ПО
        ГодДокумента УБЫВ,
        НомерБезПрефикса УБЫВ
    

    Здесь &ТекущаяДата является параметром запроса, который мы устанавливаем перед его выполнением, обычно как ТекущаяДата() или НачалоГода(ТекущаяДата()).

  6. Сложные запросы с группировкой по префиксам:

    В случае, если у нас действительно очень сложная система префиксов и нет возможности их стандартизировать, мы можем рассмотреть многоэтапные запросы. Мы можем сначала получить список всех уникальных префиксов, затем для каждого префикса найти максимальный номер (после отделения числовой части), а затем уже из этих максимальных номеров выбрать глобально максимальный, возможно, с учетом МоментаВремени или Даты.

    Такой запрос будет весьма сложным и ресурсоемким, например, он может включать в себя вложенные запросы или временные таблицы. Это может быть что-то вроде:

    1. Получаем все номера и пытаемся "распарсить" их на префикс и числовую часть.
    2. Группируем по префиксу и находим максимальную числовую часть для каждого префикса.
    3. Затем из этих максимальных значений выбираем общий максимум, учитывая год и, возможно, МоментВремени документа.

    Это крайний вариант, который следует использовать только в случае крайней необходимости, поскольку его сложность и производительность могут быть низкими.

Общие рекомендации для наведения порядка в нумерации

Для обеспечения стабильности и надежности системы нумерации документов, мы настоятельно рекомендуем следующее:

  1. Стандартизация префиксов: Мы должны стремиться к тому, чтобы префиксы были одинаковой длины и имели предсказуемую структуру. Если требуется добавить дополнительный признак (например, букву "П"), лучше делать это после основного префикса, и, возможно, с фиксированным разделителем, чтобы числовая часть всегда начиналась с одного и того же символа. Например, вместо "ОРГ1-1" и "ОРГ1-П-1", лучше "ОРГ1-0000001" и "ОРГ1П-0000001".

  2. Избегание ручной нумерации через запросы: Максимально используем встроенные механизмы платформы 1С. Они разработаны для корректной работы в многопользовательской среде и обеспечивают уникальность номеров без нашего прямого вмешательства в логику поиска "последнего".

  3. Использование механизма подписок на события: Для реализации сложной логики формирования номера (например, зависящей от документа-основания) всегда используем обработчики события ПриУстановкеНовогоНомера. Это позволяет системе самой определить следующий номер, а мы лишь корректируем его формат и префикс, согласно нашим правилам. Это намного надежнее, чем самостоятельно искать и увеличивать последний номер.

Таким образом, мы видим, что хотя существуют различные способы получения последнего номера документа с помощью запросов, они часто оказываются сложными, ненадежными и неэффективными в долгосрочной перспективе, особенно при наличии меняющихся префиксов и создания документов "задним числом". Наиболее правильным и устойчивым решением является использование стандартных механизмов платформы 1С:Предприятие, дополненных программной логикой в обработчиках событий, если это требуется для нашей бизнес-логики. Это позволит нам обеспечить надежную и корректную нумерацию документов в системе.

← На главную