При разработке и сопровождении конфигураций на платформе 1С:Предприятие программисты регулярно сталкиваются с необходимостью генерации или изменения уникальных идентификаторов, артикулов, инвентарных номеров или кодов документов, представленных в виде строк. Типичным примером является задача, когда исходный номер имеет строковый тип с определенной структурой, например, буквенно-цифровой префикс и числовой суффикс, разделенные двоеточием или другим символом (например, М000032433:1 или 98732091734:92834). Наша цель — корректно увеличить числовое значение после разделителя на единицу (инкрементировать его), сохранив при этом общую структуру строки.
На первый взгляд задача кажется тривиальной, однако при ее решении на практике возникает множество нюансов: от ограничений старых версий платформы (или работы в режиме совместимости) до проблем потери ведущих нулей и появления нежелательных неразрывных пробелов при преобразовании больших чисел в строку. В этой статье мы подробно разберем все возможные сценарии решения данной задачи, проанализируем потенциальные ошибки и предложим оптимальные алгоритмы для различных условий работы.
Прежде чем перейти к написанию кода, давайте разберем, почему стандартные подходы могут вызвать затруднения. В современных версиях платформы 1С:Предприятие (начиная с версии 8.3.6) присутствуют очень удобные встроенные функции для работы со строками, такие как СтрРазделить, СтрСоединить и СтрНайти с возможностью поиска с конца строки. Однако, если ваша конфигурация работает на старой платформе или использует режим совместимости с версиями 8.2 или ранними 8.3 (например, 8.3.5), эти функции будут вызывать ошибки компиляции вида Процедура или функция с указанным именем не определена (СтрРазделить).
Второй важный аспект — поведение платформы при работе с числами. При неявном преобразовании большого числа в строку 1С автоматически добавляет неразрывные пробелы в качестве разделителей групп разрядов. Например, число 1000 может превратиться в строку "1 000". Если такую строку попытаться повторно преобразовать в число или использовать в коде без очистки, это приведет к ошибкам. Для борьбы с этим явлением мы обязательно должны использовать явное форматирование.
Третий аспект — это сохранение ведущих (лидирующих) нулей. Если исходный суффикс выглядит как 0001, то обычное сложение Число("0001") + 1 вернет нам число 2. При простом приведении к строке мы получим "2" вместо ожидаемого "0002". Мы рассмотрим, как изящно решить и эту проблему.
Рассмотрим ситуацию, когда мы ограничены старой платформой или режимом совместимости, где недоступны современные функции работы со строками. В этом случае для поиска разделителя нам придется использовать стандартную функцию Найти, а для выделения частей строки — функции Лев и Сред.
Давайте разберем по шагам алгоритм, который безопасно находит двоеточие, корректно выделяет правую числовую часть, увеличивает ее на единицу и собирает строку обратно. Если двоеточие отсутствует, алгоритм должен либо просто инкрементировать число (если вся строка — число), либо добавить суффикс :1.
Функция ИнкрементНомераКлассический(ВходящийНомер) Экспорт
Если Не ЗначениеЗаполнено(ВходящийНомер) Тогда
Возврат "1";
КонецЕсли;
// Ищем позицию разделителя ":"
ПозицияРазделителя = Найти(ВходящийНомер, ":");
Если ПозицияРазделителя = 0 Тогда
// Если двоеточия нет, проверяем, является ли вся строка числом
Попытка
ЧисловоеЗначение = Число(ВходящийНомер);
Возврат Формат(ЧисловоеЗначение + 1, "ЧГ=0");
Исключение
// Если строка содержит буквы, просто добавляем суффикс ":1"
Возврат ВходящийНомер + ":1";
КонецПопытки;
Иначе
// Выделяем левую часть (префикс вместе с двоеточием)
Префикс = Лев(ВходящийНомер, ПозицияРазделителя);
// Выделяем правую часть после двоеточия
Суффикс = Сред(ВходящийНомер, ПозицияРазделителя + 1);
Попытка
ЧисловоеЗначение = Число(Суффикс);
НовыйСуффикс = Формат(ЧисловоеЗначение + 1, "ЧГ=0");
Возврат Префикс + НовыйСуффикс;
Исключение
// Если после двоеточия не число, оставляем как есть или обрабатываем ошибку
Возврат ВходящийНомер + "1";
КонецПопытки;
КонецЕсли;
КонецФункции
Этот код абсолютно безопасен, не использует никаких современных методов и гарантированно отработает даже на платформе 1С:Предприятие 8.1 или 8.2 в толстом клиенте обычного приложения. Обратите внимание на использование параметра форматирования ЧГ=0 — это критически важно для предотвращения появления пробелов-разделителей разрядов при работе с большими числами (например, при переходе номера за пределы 999).
Проанализируем интересное решение, предложенное разработчиками на форумах, позволяющее выполнить операцию инкрементирования буквально в одну строчку. Этот подход изящен тем, что он избегает ветвления кода и конструкции Попытка...Исключение, добавляя виртуальный разделитель к проверяемой строке при поиске.
Посмотрим на пример реализации такого подхода:
НовыйНомер = Лев(СтарыйНомер, Найти(СтарыйНомер + ":", ":") - 1) + ":" + Формат(Число("0" + Сред(СтарыйНомер, Найти(СтарыйНомер + ":", ":") + 1)) + 1, "ЧГ=0");
Давайте подробно разберем, как работает эта конструкция:
СтарыйНомер + ":" гарантирует, что символ двоеточия точно будет найден функцией Найти, даже если изначально его в строке не было. Это страхует нас от возврата нуля и некорректного обрезания строки функцией Лев.Лев(СтарыйНомер, Найти(СтарыйНомер + ":", ":") - 1) возвращает всю левую часть номера до первого двоеточия."0" + Сред(СтарыйНомер, Найти(СтарыйНомер + ":", ":") + 1) берет все символы после двоеточия. Если двоеточия не было, функция Сред вернет пустую строку, которая благодаря сложению с префиксом "0" превратится в строку "0".Число(), увеличивается на 1 и форматируется без разделителей разрядов. Таким образом, если номера после двоеточия не было, система автоматически подставит единицу.Если перед вами стоит задача разработки под современные конфигурации, где режим совместимости отключен или установлен на уровне не ниже 8.3.6, мы можем задействовать всю мощь новых встроенных функций платформы. В частности, функции СтрРазделить и СтрСоединить значительно упрощают код разбора сложных строк.
Рассмотрим пример функции инкрементирования суффикса с использованием встроенных массивов подстрок:
Функция ИнкрементНомераСовременный(ВходящийНомер) Экспорт
Если Не ЗначениеЗаполнено(ВходящийНомер) Тогда
Возврат "1";
КонецЕсли;
// Разделяем строку в массив по символу двоеточия
МассивЧастей = СтрРазделить(ВходящийНомер, ":", Ложь);
// Получаем индекс последнего элемента массива
ИндексПоследнего = МассивЧастей.Количество() - 1;
Попытка
// Преобразуем последний элемент в число и увеличиваем его
ЧисловоеЗначение = Число(МассивЧастей[ИндексПоследнего]);
МассивЧастей[ИндексПоследнего] = Формат(ЧисловоеЗначение + 1, "ЧГ=0");
Исключение
// Если последний элемент не число, значит двоеточие отсутствовало или суффикс не числовой
Если ИндексПоследнего = 0 Тогда
// Двоеточия не было вообще, добавляем элемент "1" в конец
МассивЧастей.Добавить("1");
Иначе
// Суффикс после двоеточия не число, просто дописываем "1" в конец
МассивЧастей[ИндексПоследнего] = МассивЧастей[ИндексПоследнего] + "1";
КонецЕсли;
КонецПопытки;
// Соединяем массив обратно в строку через двоеточие
Возврат СтрСоединить(МассивЧастей, ":");
КонецФункции
Преимущество этого метода заключается в том, что он корректно работает со строками, содержащими более одного двоеточия. Например, если ваш номер имеет вид М001:Склад1:45, данный алгоритм корректно выделит последнюю часть (45), увеличит ее до 46 и вернет строку М001:Склад1:46.
Очень часто в информационных базах 1С используются коды с фиксированной разрядностью суффикса. Например, суффикс должен всегда состоять из 5 символов: М000032433:00001. При инкрементировании такого номера обычные методы превратят его в М000032433:2, нарушив сортировку и структуру кодов справочника. Нам необходимо сохранить исходную разрядность.
Давайте разработаем алгоритм, который динамически вычисляет длину исходного суффикса и дополняет инкрементированное число нулями слева до нужной длины.
Функция ИнкрементССохранениемНулей(ВходящийНомер) Экспорт
ПозицияРазделителя = Найти(ВходящийНомер, ":");
Если ПозицияРазделителя = 0 Тогда
Возврат ВходящийНомер + ":00001"; // Дефолтный шаблон при отсутствии суффикса
КонецЕсли;
Префикс = Лев(ВходящийНомер, ПозицияРазделителя);
Суффикс = Сред(ВходящийНомер, ПозицияРазделителя + 1);
ДлинаСуффикса = СтрДлина(Суффикс);
Попытка
ЧисловоеЗначение = Число(Суффикс) + 1;
// Используем форматное конструирование:
// ЧЦ - общая длина, ЧВН - выводить лидирующие нули, ЧГ=0 - без пробелов
ФорматнаяСтрока = "ЧЦ=" + ДлинаСуффикса + "; ЧВН=; ЧГ=0";
НовыйСуффикс = Формат(ЧисловоеЗначение, ФорматнаяСтрока);
Возврат Префикс + НовыйСуффикс;
Исключение
Возврат ВходящийНомер + "1";
КонецПопытки;
КонецФункции
Проанализируем работу этого алгоритма. Если исходный суффикс равен 00239 (длина 5 символов), при увеличении на единицу мы получим число 240. Форматная строка примет вид "ЧЦ=5; ЧВН=; ЧГ=0", что заставит платформу вывести число 240 как 00240. Структура кодирования полностью сохранена!
Если мы работаем на старой платформе под управлением операционной системы Windows, отличной альтернативой громоздкому разбору строк является использование регулярных выражений с помощью COM-объекта VBScript.RegExp. Регулярные выражения позволяют в несколько строк кода найти число на конце строки и заменить его новым инкрементированным значением.
Рассмотрим реализацию этого метода:
Функция ИнкрементЧерезRegExp(ВходящийНомер) Экспорт
RegExp = Новый COMОбъект("VBScript.RegExp");
// Шаблон "\d+$" ищет последовательность цифр в самом конце строки
RegExp.Pattern = "\d+$";
Если RegExp.Test(ВходящийНомер) Тогда
Matches = RegExp.Execute(ВходящийНомер);
СтарыйСуффикс = Matches.Item(0).Value;
ДлинаСуффикса = СтрДлина(СтарыйСуффикс);
НовоеЧисло = Число(СтарыйСуффикс) + 1;
// Форматируем с сохранением исходной длины и ведущих нулей
НовыйСуффикс = Формат(НовоеЧисло, "ЧЦ=" + ДлинаСуффикса + "; ЧВН=; ЧГ=0");
// Производим замену старого суффикса на новый в исходной строке
Возврат RegExp.Replace(ВходящийНомер, НовыйСуффикс);
Иначе
// Если на конце строки нет числа, добавляем ":1"
Возврат ВходящийНомер + ":1";
КонецЕсли;
КонецФункции
Этот метод чрезвычайно надежен и лаконичен. Он автоматически определяет числовую часть на конце строки независимо от того, какой разделитель используется (двоеточие, дефис, косая черта или даже если разделителя нет вообще). Однако помните о важном ограничении: COM-объекты создаются только в среде операционной системы Windows и не будут работать, если сервер 1С запущен под Linux.
В большинства современных и типовых конфигураций 1С (таких как Бухгалтерия предприятия, Управление торговлей, ERP) уже интегрирована Библиотека Стандартных Подсистем (БСП). В составе БСП есть множество готовых вспомогательных функций общего назначения, которые избавляют программиста от необходимости писать собственные велосипеды.
Для разбора строк на старых платформах мы можем воспользоваться общим модулем ОбщегоНазначенияКлиентСервер. Рассмотрим пример:
Функция ИнкрементЧерезБСП(ВходящийНомер) Экспорт
// Используем аналог СтрРазделить из БСП для старых платформ
МассивЧастей = ОбщегоНазначенияКлиентСервер.РазложитьСтрокуВМассивПодстрок(ВходящийНомер, ":");
ИндексПоследнего = МассивЧастей.Количество() - 1;
Если ИндексПоследнего < 0 Тогда
Возврат "1";
КонецЕсли;
Попытка
ЧисловоеЗначение = Число(МассивЧастей[ИндексПоследнего]) + 1;
// Используем функцию дополнения строки нулями слева из БСП
НовыйСуффикс = Строка(ЧисловоеЗначение);
ДлинаИсходного = СтрДлина(МассивЧастей[ИндексПоследнего]);
МассивЧастей[ИндексПоследнего] = ОбщегоНазначенияКлиентСервер.ДополнитьСтроку(НовыйСуффикс, ДлинаИсходного, "0", "Слева");
Исключение
Если ИндексПоследнего = 0 Тогда
МассивЧастей.Добавить("1");
Иначе
МассивЧастей[ИндексПоследнего] = МассивЧастей[ИндексПоследнего] + "1";
КонецЕсли;
КонецПопытки;
// Соединяем массив обратно в строку
Возврат ОбщегоНазначенияКлиентСервер.СтрокаИзМассиваПодстрок(МассивЧастей, ":");
КонецФункции
Использование функций БСП гарантирует высокую стабильность кода, кроссплатформенность (код будет одинаково работать как под Windows, так и под Linux) и соответствие стандартам разработки фирмы 1С.
Мы подробно проанализировали различные варианты решения задачи инкрементирования строкового номера на платформе 1С:Предприятие. Подводя итог, дадим рекомендации по выбору оптимального метода:
Лев/Сред/Найти) или Решение 6 (с использованием БСП).СтрРазделить и СтрСоединить.Всегда помните об опасности неявного преобразования чисел в строки и не забывайте использовать параметр ЧГ=0 в функции Формат, чтобы обезопасить базу данных от появления неразрывных пробелов в кодах и номерах объектов.