Как реализовать нестандартную нумерацию подчиненных документов через дробь в 1С 8.3

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В процессе разработки бизнес-логики в среде «1С:Предприятие 8.3» программисты часто сталкиваются с требованиями пользователей формировать номера документов по сложным правилам. Типичный пример — создание базового документа с номером вида 04.0001 (где 04 — номер месяца, а 0001 — уникальный порядковый номер) и его последующих «подчиненных» или корректирующих документов с суффиксами через косую черту (дробь), например: 04.0001/1, 04.0001/2.

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

Почему стандартная автонумерация 1С не подходит?

Давайте выясним причину, по которой стандартный механизм автонумерации платформы неприменим для документов с суффиксами вида /1, /2. Дело в том, как именно платформа 1С анализирует номера при создании нового объекта:

  1. Платформа сканирует существующие номера в базе данных и делит строку номера справа налево.
  2. Она ищет первую группу цифр в конце строки и считает ее числовой частью, а все символы слева от нее (включая буквы, точки, дефисы и косую черту) — префиксом.
  3. Если в базе появляется документ с номером 04.0001/1, система определит, что префикс документа равен 04.0001/, а числовая часть — 1.
  4. При создании следующего абсолютно независимого базового документа автонумератор платформы попытается продолжить эту последовательность и автоматически предложит номер 04.0001/2 вместо ожидаемого нового базового номера 04.0002. Вся стандартная нумерация в базе мгновенно нарушится.

Именно поэтому для реализации подобных схем нам необходимо полностью отказаться от стандартной автонумерации для этого типа документов (или существенно её ограничить) и перейти на программное управление процессом.

Способ 1. Полная ручная нумерация документов с помощью запроса

Самый очевидный способ решить задачу — отключить автонумерацию в свойствах документа в Конфигураторе и формировать номер самостоятельно перед записью объекта. Разберем по шагам, как это реализовать.

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

Рассмотрим пример функции, которая генерирует новый базовый номер:


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

Для создания подчиненного документа логика меняется. Нам необходимо передать в функцию базовый документ, определить все его существующие подчиненные номера с косой чертой, найти максимальный суффикс и прибавить к нему единицу:


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

Критический нюанс: Транзакционные блокировки при ручной нумерации

Разработчики часто совершают опасную ошибку, внедряя ручную нумерацию запросами без учета многопользовательской среды. Представим ситуацию: два бухгалтера одновременно нажимают кнопку «Записать» на новых документах. Обе сессии выполняют вышеописанный запрос практически одновременно. Система вернет им одинаковый максимальный номер, например 04.0001. Обе сессии увеличат его на 1 и получат 04.0002. В результате при записи один из пользователей гарантированно получит ошибку неуникальности номера, либо база данных зависнет из-за взаимной блокировки (Deadlock).

Чтобы избежать подобных проблем, перед выполнением запроса генерации номера необходимо установить управляемую блокировку на пространство данных этого документа:


Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("Документ.НашДокумент");
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
Блокировка.Заблокировать();

// Только после этого выполняем запрос получения максимального номера
// и записываем новый документ. Блокировка снимется автоматически при завершении транзакции.

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

Способ 2. Лучшая практика — Использование вспомогательного регистра сведений

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

Профессиональные разработчики 1С рекомендуют использовать для генерации номеров вспомогательный независимый Регистр сведений (назовем его ОчередныеНомераДокументов).

Структура регистра сведений:

Рассмотрим, как красиво и производительно работает этот механизм. При создании документа мы обращаемся не к таблице документов, а к конкретной строке этого небольшого регистра. Блокировка накладывается точечно — только на одну запись регистра по ключевым измерениям!

Приведем пример программного кода для генерации очередного номера через регистр сведений:


Функция ПолучитьОчереднойНомерЧерезРегистр(ТекущийПериод, БазовыйНомерДока = "") Экспорт
    
    // 1. Устанавливаем точечную блокировку на запись регистра
    Блокировка = Новый БлокировкаДанных;
    ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.ОчередныеНомераДокументов");
    ЭлементБлокировки.УстановитьЗначение("ПериодНумерации", ТекущийПериод);
    ЭлементБлокировки.УстановитьЗначение("БазовыйНомер", БазовыйНомерДока);
    Блокировка.Заблокировать();
    
    // 2. Считываем текущее значение номера
    Отбор = Новый Структура("ПериодНумерации, БазовыйНомер", ТекущийПериод, БазовыйНомерДока);
    ЗначенияРесурсов = РегистрыСведений.ОчередныеНомераДокументов.Получить(Отбор);
    
    Если ЗначенияРесурсов.ТекущийНомер = Неопределено Тогда
        НовоеЧислоНомера = 1;
    Иначе
        НовоеЧислоНомера = ЗначенияРесурсов.ТекущийНомер + 1;
    EndIf;
    
    // 3. Записываем новое значение в регистр
    МенеджерЗаписи = РегистрыСведений.ОчередныеНомераДокументов.СоздатьМенеджерЗаписи();
    МенеджерЗаписи.ПериодНумерации = ТекущийПериод;
    МенеджерЗаписи.БазовыйНомер = БазовыйНомерДока;
    МенеджерЗаписи.ТекущийНомер = НовоеЧислоНомера;
    МенеджерЗаписи.Записать(Истина);
    
    // 4. Формируем строковое представление номера
    Если ПустаяСтрока(БазовыйНомерДока) Тогда
        // Формируем базовый номер: "04.0001"
        ПрефиксМесяца = Сред(ТекущийПериод, 5, 2);
        СтрокаНомера = ПрефиксМесяца + "." + Формат(НовоеЧислоНомера, "ЧГ=0; ЧЦ=4; ЧВ=0");
    Иначе
        // Формируем подчиненный номер: "04.0001/1"
        СтрокаНомера = БазовыйНомерДока + "/" + Формат(НовоеЧислоНомера, "ЧГ=0");
    КонецЕсли;
    
    Возврат СтрокаНомера;
    
КонецФункции

Этот подход признан лучшей практикой в промышленной разработке на платформе 1С, так как он сводит к минимуму нагрузку на СУБД и полностью предотвращает конфликты блокировок у пользователей.

Способ 3. Подход БСП — Разделение системного номера и номера для печати

Зачастую бухгалтеры просят дробные номера исключительно ради красоты печатных форм (счетов-фактур, актов, накладных) и соответствия требованиям законодательства. При этом внутри базы данных 1С системные номера могут оставаться абсолютно стандартными и сквозными.

Если в вашей конфигурации используется Библиотека стандартных подсистем (БСП), мы рекомендуем использовать встроенные механизмы модификации печатных номеров. Этот подход позволяет полностью избежать изменения стандартной автонумерации:

  1. В самой информационной базе документы сохраняют красивую стандартную сквозную нумерацию, например 0000-000412, 0000-000413. Это гарантирует максимальное быстродействие СУБД и бесконфликтную работу пользователей.
  2. Для связи подчиненного документа с базовым используется реквизит на форме или стандартная ссылка (например, реквизит ДокументОснование).
  3. При выводе документа на печать используется событие переопределяемого модуля БСП (например, общего модуля ПрефиксацияОбъектовКлиентСерверПереопределяемый) или функции форматирования номера в модуле менеджера документа. Системный номер программно преобразуется к нужному виду непосредственно перед выводом на печать.

Рассмотрим пример функции, преобразующей системный номер на печати:


Функция ПолучитьНомерНаПечать(ДокументОбъект) Экспорт
    
    СистемныйНомер = ОбщегоНазначенияКлиентСервер.ПолучитьНомерНаПечать(ДокументОбъект.Номер);
    
    Если ЗначениеЗаполнено(ДокументОбъект.ДокументОснование) Тогда
        // Документ подчиненный, сформируем дробный номер для печати
        НомерОснования = ОбщегоНазначенияКлиентСервер.ПолучитьНомерНаПечать(ДокументОбъект.ДокументОснование.Номер);
        
        // Вычисляем порядковый номер среди подчиненных документов
        ПорядковыйНомер = ПолучитьПорядковыйНомерПодчиненного(ДокументОбъект);
        
        Возврат НомерОснования + "/" + ПорядковыйНомер;
    Иначе
        // Базовый документ, просто форматируем системный номер под требования
        Возврат ПреобразоватьНомерПодФорматМесяца(СистемныйНомер, ДокументОбъект.Дата);
    КонецЕсли;
    
КонецФункции

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

Важная деталь: Контроль длины поля в Конфигураторе

Какой бы из способов вы ни выбрали, при проектировании структуры «Базовый/Подчиненный» критически важно закладывать достаточную длину стандартного реквизита Номер в свойствах метаданных документа.

По умолчанию в большинстве типовых конфигураций 1С длина номера ограничена 11 символами. Проанализируем длину нашей будущей строки номера:

При превышении лимита длины номера платформа начнет нещадно обрезать правые символы. В результате номера документов начнут дублироваться, вызывая критические ошибки при записи. Чтобы застраховаться от этого сценария, обязательно увеличьте длину реквизита Номер в свойствах документа в Конфигураторе с запасом (например, до 15–20 символов).

Резюме

Реализация нестандартной нумерации документов с использованием дробей требует от разработчика вдумчивого подхода:

← На главную