Привет, Хабр! (И тебе, 1С-ник, который открыл статью только чтобы убедиться, что пока точно не надо изучать новый продукт. И тебе, питонист, который сейчас будет морщиться. И тебе, мобильный разработчик, который уверен, что без Kotlin/Swift нормальное приложение не родится - держи кружку крепче.)

Эта статья будет посвящена технологии, на которой можно написать своё мобильное приложение за пару дней. И я вам на примере реализованного мною полноценного проекта логистического приложения покажу, что сейчас из себя представляет эта технология на нашей родной КИРИЛЛИЦЕ, и что на ней можно сделать прямо сейчас. И самое главное, как…

Статья получилась большой и охватывающей множество областей разработки и знание 1С здесь вообще не нужно. А для большего понимания я буду всё сравнивать с популярными ЯП. Что бы вы - мои любители латиницы познакомились, сравнили и дали вердикт - это кринж или норм.

Маленькая ремарка для тех, кто читает меня впервые. Я не 1С-ник в классическом смысле (вообще нет). Я Python и JS разработчик (получается full-stack), который по иронии судьбы пишет целый проект на этом чуде-юде. Так что смотреть на всё я буду из своего опыта, а не из методички (тем болеем инфы там ой как не хватает). Так что думаю будет интересно)))

Глава 0. Что вообще за проект и почему это не “жёлтая 1С”

Есть одно очень крупное предприятие в России. У предприятия - своя логистика (логично): грузовики, водители, диспетчеры, мастера на точках погрузки-разгрузки. Всё это крутится вокруг внешней системы УАТ (управление автотранспортом), которая живёт своей жизнью и общается с внешним миром через REST API - местами логичный, местами такой, что хочется позвонить его автору и молча подышать в трубку.

Задача звучала так: нужно мобильное приложение, через которое:

  • Мастер создаёт заказ на перевозку - тыкает точки маршрута на карте, подбирает автомобиль, отправляет в работу, редактирует заказы;

  • Водитель видит свои заказы, жмёт на "Принять заказ", "Прибыл на точку", "Погрузка начата", "Убыл" - и всё это в реальном времени улетает в УАТ;

  • Мастер на точке подтверждает погрузку/разгрузку и получает пуши, когда к нему едет машина;

  • Диспетчера мониторят все заказы через более детальный мониторинг заказов и так же может редактировать;

  • А администратор настраивает всю эту кухню: пользователей, интеграцию, сертификаты для push-уведомлений и даже тексты этих самых пушей.

То есть как вы уже понимаете это не "Hello, World с кнопочкой", а полноценное production-приложение: карта с маркерами, машина состояний заказов, push-уведомления через FCM, живая двусторонняя интеграция с внешним API и мультиролевой интерфейс, который на телефоне, планшете и десктопе выглядит по-разному.

А теперь то, ради чего половина из вас и открыла статью.

Всё это написано не на Kotlin, не на Swift, не на React Native и даже не на Flutter.

Внешний вид на мобильном устройстве просмотра заказов. Да это 1С...
Внешний вид на мобильном устройстве просмотра заказов. Да это 1С...

Это написано на 1С:Элемент - новом языке и платформе от 1С (называю новым так как даже два года для языка программирования - это ещё очень мало).

А Элемент это те ещё компромиссы…

Вот так выглядит в Web-е создание/редактирование заказа
Вот так выглядит в Web-е создание/редактирование заказа

Зачем я это пишу?

Во-первых, про 1С:Элемент на Хабре почти ничего нет (не считая моих статей конечно же). В основном здесь пресс-релизы в духе "революционная платформа", а живого production-кода с граблями - ноль! Можно посмотреть и в интернете, но даже когда там вводишь "1С:Элемент", то мои статьи вылазят на первой странице поиска. А полноценных и честных разборов проектов нет, стесняются что ли? Исправляю это.

- Мама я в интернете!
- Мама я в интернете!

Во-вторых, мне реально есть что показать: null-safety, RPC клиент-сервер из коробки, генерацию UI прямо в коде, машину состояний и интеграцию, которая переживает падение внешнего сервера. Кое-что меня восхитило. Кое-что заставило вставить Пауза(120мс) и сделать вид, что так и было (каюсь, и про грабли тоже будет и про смирение, а куда без этого).

Короче покрутим язык со всех сторон в призме проекта.

Проект мы назвали titransflow. Работал над ним я два месяца, и работаю до сих пор. На старте была только интеграция с УАТ по HTTP - и всё. Тут, в отличие от моего прошлого опыта, уже была версия платформы 9.1, а не 5.4. Изменился ли язык за четыре версии? Не сказать. Пара доработок по отдельным направлениям, документация местами всё так же не стыкуется с реальностью. Всё как обычно. Печально.

Короче, думаю, вводной части хватит. По чуть-чуть начну погружать вас в данную технологию и по мере понимания происходящего переходить к реализации проекта. Поехали.

Глава 1. Null-safety, дженерики и прочий "взрослый" синтаксис

Kotlin-разработчики, вам сюда, будете смеяться от узнавания. В Python я долго любил нестрогую типизацию. Потом, пересев на Элемент, начал возмущаться на строгую: мне виднее, что летит через функцию, не технологии решать. А поработав над полноценным проектом, я понял, зачем строгость нужна. Вот переменная, которая обречена всю жизнь быть массивом структур СтрокаМаршрутаЗаказа:

пер ОбновленныеТочкиМаршрута: Массив<СтрокаМаршрутаЗаказа> 
Вот так выглядит структура в среде разработки
Вот так выглядит структура в среде разработки

Опциональная цепочка и дефолт в одну строку - прямо из учебника по Swift/Kotlin:

метод ТекущийСтатусТочки(Заказ: Заказы.Ссылка?, ИдентификаторСтрокиЗаказа: Ууид?): ВидыСтатусовТочекЗаказов
   …
    возврат РезультатЗапроса.ЕдинственныйИлиНеопределено()?.Статус ?? ВидыСтатусовТочекЗаказов.Новая
; 

ЕдинственныйИлиНеопределено() вернёт либо строку результата, либо Неопределено. ?.Статус безопасно достаёт поле, а ?? подставляет значение по умолчанию, если записи не нашлось. Одна строка вместо блока с вложенными проверками. Заодно обратите внимание, что метод имеет тип возвращаемого значения - ВидыСтатусовТочекЗаказов.

Пока всё просто. Смысл я думаю улавливаете.

А вот force-unwrap. Когда я уверен, что значение точно есть, ставлю ! и беру ответственность на себя:

Данные.СозданныйЗаказ = Перевозки::Сервисы::DTO.ОтправитьЗаказВУАТ(Данные)
знч ОбъектЗаказа = ДанныеЗаказа.СозданныйЗаказ!.ЗагрузитьОбъект()

Заказ тут гарантированно создан, поэтому ставлю ! и беру ответственность на себя. Это как сказать компилятору “я те зуб даю”. Совру - прилетит в рантайме. Это же 1С - здесь всё должно быть по пацански))

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

Тип: Дата|ДатаВремя|Момент|Время|ЗакрытыйДиапазон<Дата>|ЗакрытыйДиапазон<ДатаВремя>|ЗакрытыйДиапазон<Момент>|ЗакрытыйДиапазон<Время>|?

Восемь типов через палочку и ? на закуску. Гибко. Но когда встречаешь такое в чужом коде в пятницу вечером, начинаешь тихо философствовать о смысле бытия. Формально можно было поставить тип неизвестно, но тогда по всему коду пришлось бы городить обработки вида Дата как Дата или Дата как Момент. А ради чего всё это? Ради общего компонента, который принимает любую дату. Грустно. Так что строгая типизация это конечно хорошо, но не всегда уместна...

Глава 2. У нас тут и Лямбды есть и чего только нет идите к нам

Функциональщина тут живёт полноценно, а не для галочки.

Эй народ, у нас тоже есть map!

Фильтрация и маппинг в модуле прав - достаём из массива пользователей с конкретной ролью:

знч ПользователиСРолью = ПользователиИРоли
    .Фильтровать(Стр -> Стр.ТипСотрудника == Ключ.Роли)
    .Преобразовать(Стр -> Стр.Пользователь)

Python-разработчик прочитает это как filter().map(), Kotlin-разработчик - как .filter{}.map{}, а 1С-ник со стажем прочитает это и заплачет, потому что у него только Для Каждого ... Цикл на семь строк ради одной трансформации, а у его младшего брата Элемента всё продумано. Нравится, берём))

Глава 3. Встроенный SQL как типизированный литерал (и почему это гениально)

Здесь Элемент показал, что корни 1С не забыл, но переосмыслил (ну а что, в 1С удобно всё с запросами, да, есть но, ну а где их нет). Язык запросов живёт прямо в коде, и это не строка, а типизированный литерал с проверкой на этапе компиляции. Запрос из машины состояний - достаём последний статус заказа:

знч Запрос = Запрос{
  ВЫБРАТЬ ПЕРВЫЕ 1
    СтатусыЗаказов.Статус КАК Статус
  ИЗ
    СтатусыЗаказов.СрезПоследних КАК СтатусыЗаказов
  ГДЕ
    СтатусыЗаказов.Заказ == %{Заказ}
}
исп РезультатЗапроса = Запрос.Выполнить()

Тут важны две вещи. Первая - %{Заказ} подставляет параметр безопасно, а не склеивает строку (питонисты и джависты поняли, о чём я). Вторая - исп. Это using-блок, ресурс сам закроется после использования. Можно и без него, просто выполнив Запрос{}.Выполнить().

А вот совсем красиво - подстановка результата лямбды над коллекцией прямо в ГДЕ:

ГДЕ
  Пользователь В (%{Пользователи.Преобразовать(Стр -> Стр)})
  И ТипСотрудника В (%{Ключи.Преобразовать(Стр -> Стр.Роли)})

Прямо в параметре запроса массив ключей превращается в массив ролей, и всё это типизировано. Мне это сэкономило кучу промежуточных переменных, да и для вас выглядит понятно.

Справедливости ради, язык запросов умеет быть и страшным. Есть метод, который ищет дату последнего изменения статусов по сотруднику: три подзапроса, ОБЪЕДИНИТЬ ВСЕ, НЕ СУЩЕСТВУЕТ, соединения по разнарядке, полэкрана текста. Но виноват тут не язык. Просто таков путь...)

Глава 4. Контракты и DI - то, чего я не ждал, а вы?

Здесь у кого-то должна отвиснуть челюсть. В проекте работает честная инверсия зависимостей через механизм контрактов.

Задача была такая. Подсистема Перевозки умеет вести заказы и гонять их по статусам, но не должна знать, через какую внешнюю систему заказы уходят. Сегодня это УАТ, завтра что угодно. Поэтому объявляется контракт, по сути, интерфейс:

абстрактный метод НачатьДвижениеКТочке(
    Заказ: Заказы.Ссылка,
    Точка: ТочкиДоставки.Ссылка,
    Порядок: Число,
    Широта: Число?,
    Долгота: Число?,
    Одометр: Число?): РезультатИсполненияЗаказа?

Реализация лежит в отдельной подсистеме - в модуле интеграции с УАТ, помеченная @Реализация:

@Реализация
@ВПроекте
метод НачатьДвижениеКТочке(Заказ: Заказы.Ссылка, Точка: ТочкиДоставки.Ссылка, Порядок: Число, ...): РезультатИсполненияЗаказа?
    возврат ВыполнитьПереходИсполнения(Заказ, Точка, Порядок, "ON_THE_WAY", "START", ...)
;

Перевозки получают реализацию, ничего о ней не зная:

метод ИсточникиИсполненияЗаказов(): ИсполнительЗаказов
  знч СервисИсполнительЗаказов = ИсполнительЗаказов.ПолучитьСервис()
  если СервисИсполнительЗаказов == Неопределено
    выбросить новый ИсключениеВыполнения("Не реализован исполнитель заказов")
  ;
  возврат СервисИсполнительЗаказов!
;

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

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

@ВПроекте
@Реализация
метод РазделыПриложения(): ЧитаемаяКоллекция<ОсновнойИнтерфейсКлиент.ОписаниеРаздела>
  знч Разделы: Массив<ОсновнойИнтерфейсКлиент.ОписаниеРаздела>
  если ИспользованиеСпискаЗаказов()
    Разделы.Добавить(ОписаниеРазделаПолучениеЗаказа())
  ;
  если ИспользованиеРазделаСозданиеЗаказа()
    Разделы.Добавить(ОписаниеРазделаСозданиеЗаказа())
  ;
  возврат Разделы
;

А ядро приложения просто собирает всё со всех подсистем:

знч Сервисы = ОсновнойИнтерфейс.ПолучитьСервисы()
для Сервис из Сервисы
  РазделыПриложенияСведения.ДобавитьВсе(Сервис.РазделыПриложения())
;

Подсистемы не лезут друг в друга напрямую. Перевозки не зависят от Интеграции, хотя именно Интеграция реализует их контракт. В энтерпрайзе на Java или C# за такую развязку зависимостей платят архитекторам, а тут это встроено в платформу.

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

Глава 5. Клиент-сервер: единый код и магия @ДоступноСКлиента

Об этом я рассказывал в прошлой статье, но повторю, потому что для фуллстекера это выглядит как читерство: никакого отдельного бэкенда, ручного fetch и мыслей про эндпоинты.

Реальный обработчик кнопки "Принять":

метод ПринятьПриНажатии(Источник: Кнопка, Событие: СобытиеПриНажатии)
  если Заказ.Ссылка == Неопределено
    возврат
  ;
  знч Результат = ПринятьЗаказНаСервере(Заказ.Ссылка!)
  если КомандыФорм.УведомитьОИсключениеВыполнения(Результат)
    возврат
  ;
  ОбновитьСтатусИКоманды()
;

@НаСервере @ДоступноСКлиента
статический метод ПринятьЗаказНаСервере(Заказ: Заказы.Ссылка): ИсключениеВыполнения?
  возврат СостоянияЗаказов.ПринятьЗаказ(Заказ)
;

Клиентский метод (в браузере или на телефоне водителя) вызывает ПринятьЗаказНаСервере как обычную функцию. Аннотации @НаСервере @ДоступноСКлиента говорят платформе: метод выполняется на сервере, а вызывать его в принципе можешь и с клиента. RPC генерируется сам.

Отдельно отмечу подход к ошибкам. Логично, что если ошибка на сервере, то на интерфейсе мы её не отобразим, потому что не видим. По этому серверный метод возвращает ИсключениеВыполнения?, то есть ошибка едет обратно как значение, а не летит исключением через всю сеть. УведомитьОИсключениеВыполнения проверяет результат и показывает сообщение пользователю. Без этого нам бы пришлось вызывать типовую ошибку.

Конечно всё это расплата за удобство - надо постоянно держать в голове, где что выполняется. Каждый модуль помечен окружением: Клиент, Сервер или КлиентИСервер. Дёрнуть серверный справочник из клиентского кода не выйдет - синтаксическая ошибка ещё до запуска. В прошлой статье я на это ворчал, а в бою понял, что лучше поймать ошибку компиляции, чем ловить её потом.

Глава 6. Машина состояний - где живёт вся бизнес-логика

Модуль СостоянияЗаказов - сердце проекта. Взгляните на неё.

Статусы заказа и точек заказа. Что бы было понятно выглядят так (версия на середину июля).
Статусы заказа и точек заказа. Что бы было понятно выглядят так (версия на середину июля).

Доступная команда вычисляется от текущего состояния и роли пользователя одновременно:

Четвёртый параметр Истина у команды "Прибыл на точку" - это ТребуетГеопозицию.

Каждый переход защищён проверками:

метод ПрибылНаТочку(Заказ: Заказы.Ссылка): void
    ПроверитьСтатусЗаказа(Заказ, ВидыСтатусовЗаказов.ВРаботе)
    ПроверитьРоль(ТипыСотрудников.Водитель)

    знч СтрокаМаршрута = АктивнаяСтрокаМаршрута(Заказ)
    если СтрокаМаршрута == Неопределено
        выбросить новый ИсключениеВыполнения("Не найдена активная точка маршрута")
    ;
    ПроверитьСтатусТочки(Заказ, СтрокаМаршрута.ИдентификаторСтроки, ВидыСтатусовТочекЗаказов.ОжиданиеПрибытияАвтомобиля)
    СценарииЗаказов.ПрибылВТочку(Заказ, СтрокаМаршрута.ТочкаМаршрута, СтрокаМаршрута.Порядок)
    УстановитьСтатусТочки(Заказ, СтрокаМаршрута, ВидыСтатусовТочекЗаказов.ОжиданиеПогрузкиРазгрузки)
;

Метод проверяет статус заказа, роль пользователя, находит активную точку, проверяет её статус. Только после этого дёргает сценарий (который через контракт из главы 4 сходит в УАТ) и переводит точку дальше. Перевести заказ в некорректное состояние невозможно.

Глава 7. Push-уведомления - от регистрации устройства до текста в шаблоне

Набираем темп и теперь уже переходим к более интересному. На мой взгляд))

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

Сначала устройство надо зарегистрировать. При старте приложение на Android получает идентификатор подписчика уведомлений и сохраняет его:

@ВПроекте
@НаКлиенте
метод ЗарегистрироватьУстройство()
    если КлиентскоеУстройство.ВидПлатформы == ВидПлатформыКлиента.Android
        знч Идентификатор = ДоставляемыеУведомления.ПолучитьИдПодписчикаУведомлений()
        ИдентификаторыУстройств.ИдентифицироватьУстройство(Идентификатор.ИдУстройства, КлиентскоеУстройство.ВидПлатформы)
    ;
;

Кстати, почему только Android? Ведь можно и приложение для IOS выпустить. Просто на производстве у всех мастеров и водителей рабочие устройства на Android, и даже нет смысла заморачиваться с IOS. Тыдынь-тыщь...

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

Когда заказ меняет статус, машина состояний дёргает сервис уведомлений. Метод собирает участников заказа и рассылает каждому push по шаблону.

@ВПодсистеме
метод ОтправитьУведомленияПоСобытиюЗаказа(Заказ: Заказы.Ссылка, КатегорияСобытия: КатегорииУведомленийПеревозки, ИсключитьПользователя: Пользователи.Ссылка? = Неопределено)
    знч ОбъектЗаказа = Заказ.ЗагрузитьОбъект()
    знч Участники = ЗаказыСервер.ПолучитьУчастниковЗаказа(Заказ)
    для Участник из Участники
        пер ПользовательИзСотрудника = Участник.Сотрудник.ЗагрузитьОбъект().Пользователь
        если ПользовательИзСотрудника != Неопределено и ИсключитьПользователя != ПользовательИзСотрудника
            пер СтрокаФильтраСтроковогоРесурса = новый Основное::СтрокаФильтраСтроковогоРесурса(
                Наименование = "PUSH_УВЕДОМЛЕНИЕ_ПЕРЕВОЗКИ",
                КатегорияПервостепенная = КатегорияСобытия.Представление(),
                КатегорияВторостепенная = Участник.КатегорияУчастника.Представление()
            )
            Основное::УведомленияПриложения.ОтправитьУведомлениеПользователюПоШаблону(
                ПользовательИзСотрудника, СтрокаФильтраСтроковогоРесурса,
                ПараметрыШаблона = {
                    "НОМЕР": "№%{ОбъектЗаказа.Номер}",
                    ...
                }
            )
        ;
    ;
; 

Здесь важно то, что уведомление адресное. Водитель получает "Вам назначен заказ №…", диспетчер - "Водитель прибыл на точку", мастер - "Ваша заявка выполнена". Кто и что получит, решает ПолучателиУведомления в связке с ролью и событием, ну и спасибо СтрковымРесурсам о которых будет дальше.

Затем за дело принимается сама отправка уведомления...

знч Уведомления = <ДоставляемоеУведомление>[]
Уведомления.Добавить(
  новый ДоставляемоеУведомление(
    Ид = новый Ууид().ВСтроку(),
    Заголовок = Заголовок,
    Текст = Текст,
    Триггер = новый ТриггерДоставляемыхУведомленийПоСпискуПолучателей(Получатели = Получатели)
  )
)
знч ДанныеАунтификацииFcm = новый ДанныеАутентификацииFcm(
  КонстантыПриложения.ИдентификаторПриложенияПоУмолчанию(),
  СертификатыPUSH.ПолучитьСертификат().Загрузить().ОткрытьПотокЧтения().ПрочитатьКакБайты()
)
знч Результат = ОтправкаДоставляемыхУведомлений.Отправить(Уведомления, [ДанныеАунтификацииFcm])

Здесь смысла нет останавливаться и демонстрировать код целиком, я, по сути, сделал всё как в методичке только храню и получаю сертификат от Гугл фаирбейс не в ресурсах системы (так как не является данное решение безопасным), а в шифрованном хранилище. А так здесь всё просто под капотом Элемента скрыта отправка PUSH через гугл. Точно так же, как я реализовал это для примера на Python, только из коробки:

import requests
from google.oauth2 import service_account
import google.auth.transport.requests
def get_access_token():
    credentials = service_account.Credentials.from_service_account_file("ФАЙЛИК_СЕРТИФИКАТА_ГУГЛ.json", scopes=["https://www.googleapis.com/auth/firebase.messaging"])
    credentials.refresh(google.auth.transport.requests.Request())
    return credentials.token
def send_push(device_token):
    access_token = get_access_token()
    headers = {"Authorization": f"Bearer {access_token}", "Content-Type": "application/json; UTF-8",}
    payload = {"message": {"token": device_token, "notification": {"title": "Тебе пришла СМС-ка", "body": "Пора бы съездить на заказ"}}}
    response = requests.post(f"https://fcm.googleapis.com/v1/projects/{"ID_ПРОЕКТА"}/messages:send", headers=headers, json=payload)

Отдельная радость была отлаживать это всё… Push на эмуляторе Android (Да тут вам только стоит задуматься, что я могу тестировать технологию 1С на эмуляторе Андроид).

Но тут тоже не без приколов... Push на эмуляторе Android живёт по законам квантовой механики как я понял: пока не посмотришь - непонятно, пришёл он или нет. Он приходит в суперпозиции "сразу или через 10 минут или никогда". Сидишь в наушниках пишешь код и приходит уведомление которые ты уже не помнишь, когда и отправил в эмуляторе, и музыка в наушниках прерывается на громкий звон прихода PUSHа. Чистый рандом… И дёргающийся глаз. Но в 90% случаев всё нормально.

Вот так выглядят уведомления
Вот так выглядят уведомления

Глава 8. Строковые ресурсы - это вам не "файлик с текстами", а типизированный доступ

То, что выше я называется СтроковыеРесурсы, - это не встроенный механизм локализации Элемента (хотя тот присутствует в Элементе). Это моя собственная подсистема, и придумал я её из вполне конкретной боли.

Ради одной запятой в тексте уведомления или системного сообщения не хочется перекомпилировать код и выкатывать релизы. Мало-ли грамматическая ошибка… Всякое бывает…

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

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

И из этих 2-х фильтров плюс ключ привязки у нас получается то или иное сообщение.

Вот как выглядит справочник
Вот как выглядит справочник

Обращение из кода идёт через сервис, который по подсистеме и ключу достаёт актуальный текст и подставляет параметры:

@ВПроекте
метод ПолучитьСтроковыйРесурс(Фильтрация: СтрокаФильтраСтроковогоРесурса, ПараметрыШаблона: Соответствие<Строка, Строка>? = Неопределено): СтрокаЗначенийСтроковогоРесурса?
    знч РезультатЗапроса = Запрос{
        ВЫБРАТЬ ПЕРВЫЕ 1
            СтроковыеРесурсы.ОсновноеЗначение КАК ОсновноеЗначение,
            СтроковыеРесурсы.ДополнительноеЗначение КАК ДополнительноеЗначение,
            СтроковыеРесурсы.Использовать КАК Использовать
        ИЗ
            СтроковыеРесурсы КАК СтроковыеРесурсы
        ГДЕ
            СтроковыеРесурсы.Наименование == %{Фильтрация.Наименование}
            И (
                %{Фильтрация.КатегорияПервостепенная} == Неопределено
                ИЛИ СтроковыеРесурсы.КатегорияПервостепенная == %{Фильтрация.КатегорияПервостепенная}
            )
            И (
                %{Фильтрация.КатегорияВторостепенная} == Неопределено
                ИЛИ СтроковыеРесурсы.КатегорияВторостепенная == %{Фильтрация.КатегорияВторостепенная}
            )
    }.Выполнить().ЕдинственныйИлиНеопределено()
    если РезультатЗапроса != Неопределено
        пер ОсновноеЗначение = РезультатЗапроса.ОсновноеЗначение
        пер ДополнительноеЗначение = РезультатЗапроса.ДополнительноеЗначение
        пер Использовать = РезультатЗапроса.Использовать
        если ПараметрыШаблона != Неопределено
            для КлючПараметра из ПараметрыШаблона.Ключи()
                пер ШаблонЗамены = "\$%{КлючПараметра}\$"
                ОсновноеЗначение = ОсновноеЗначение.Заменить(ШаблонЗамены, ПараметрыШаблона[КлючПараметра])
                ДополнительноеЗначение = ДополнительноеЗначение.Заменить(ШаблонЗамены, ПараметрыШаблона[КлючПараметра])
            ;
        ;
        знч Образец = '\$[^$]*\$'
        для Совпадение из Образец.НайтиСовпадения(РезультатЗапроса.ДополнительноеЗначение)
            ОсновноеЗначение = ОсновноеЗначение.Заменить(Совпадение.Значение(), "")
        ;
        для Совпадение из Образец.НайтиСовпадения(РезультатЗапроса.ДополнительноеЗначение)
            ДополнительноеЗначение = ДополнительноеЗначение.Заменить(Совпадение.Значение(), "")
        ;
        возврат новый СтрокаЗначенийСтроковогоРесурса(ОсновноеЗначение, ДополнительноеЗначение, Использовать)
    ;
    возврат Неопределено
; 

Код возможно не идеальный но работает...

Здесь мы в примере видим, что мы, выполнив запрос получаем результат с подходящим строковым ресурсом. В нём могут лежать теги на те или иные объекты. Мы их находим с помощью регулярных выражений. Заменяем если данное значение имеется или просто затираем из текста если нет. И в результате получаем готовое сообщение с Заголовком и Текстом.

При первом развёртывании справочник Строковых ресурсов наполняется значениями по умолчанию из ресурсов подсистем. Дальше это уже данные, которые живут своей жизнью и правятся из интерфейса.

Тексты плашек на формах я гоняю через тот же механизм. Вот вызов сообщения на форме создания заказа, когда маршрут не заполнен:

Компоненты.ОсновноеСообщение.Сообщение(
    ОбщийМодуль.ИД_ПОДСИСТЕМЫ, ИД_ФОРМЫ, "СозданиеЗаказаНеЗавершено",
    ПараметрыШаблона, ВидПредупреждения = БиблиотекаЦветов.ВидПредупреждения.Ошибка
)

Первые аргументы - координаты ресурса (подсистема, форма, ключ), дальше параметры подстановки. Компонент ОсновноеСообщение сам сходит в подсистему строковых ресурсов, достанет текст и покажет. Ни одна формулировка не зашита в логику формы.

Глава 8. Карта на MapLibre и таймер, через WebView

Помните, в прошлой статье я ругался, что в Элементе нельзя подвинуть кнопку на три пикселя? (Я думаю не помните, но мало ли) И тогда я тут же упомянул лазейку - КонтейнерHTML, куда можно засунуть свой HTML/CSS/JS. Так вот, в этом проекте лазейка превратилась в полноценную фичу.

Карта при создании заказа на MapLibre.
Карта при создании заказа на MapLibre.

Диспетчеру нужна карта с машинами и точками маршрута. Штатной карты в Элементе, которая бы меня устроила, нет. Поэтому берём MapLibre GL и живём. Почему MapLibre? А просто потому, что красиво. Без сакрального смысла))

Внутри КонтейнерHTML лежит обычная веб-страница с картой, а общение между Элементом и JS идёт через мост:

метод ГенерацияКарты(): Строка
    пер ТемаКарты: Строка
    если КлиентскоеПриложение.ПолучитьТекущуюЦветовуюСхему() == ЦветоваяСхема.Светлая
        ТемаКарты = СтилиКарты.light_all.Представление()
    иначе
        ТемаКарты = СтилиКарты.dark_all.Представление()
    ;
    пер HTML_Карта =
    "
        <!DOCTYPE html>
        <html lang='ru'>
        ...
             /* Цвета по типам */
            .pin.from {
            background: %{БиблиотекаЦветов.ЦветRGB(БиблиотекаЦветов.Цвет(БиблиотекаЦветов.ЦветаПриложения.Оранжевый))};
            }

            .pin.to {
            background: %{БиблиотекаЦветов.ЦветRGB(БиблиотекаЦветов.Цвет(БиблиотекаЦветов.ЦветаПриложения.Синий))};
            }

            .pin.via {
            background: %{БиблиотекаЦветов.ЦветRGB(БиблиотекаЦветов.Цвет(БиблиотекаЦветов.ЦветаПриложения.СреднеТемноСерый))};
            }
        …
        sources: {
           'carto-light': {
           type: 'raster',
           tiles: [
              'https://a.basemaps.cartocdn.com/%{ТемаКарты}/{z}/{x}/{y}@2x.png',
              'https://b.basemaps.cartocdn.com/%{ТемаКарты}/{z}/{x}/{y}@2x.png',
              'https://c.basemaps.cartocdn.com/%{ТемаКарты}/{z}/{x}/{y}@2x.png',
              'https://d.basemaps.cartocdn.com/%{ТемаКарты}/{z}/{x}/{y}@2x.png'
           ],
              tileSize: 256,
              attribution: '&copy; <a href=\"https://carto.com/\">CARTO</a>'
           }
        },
        …
        center: %{КонстантыПриложения.КоординатыЦентрированияКарты()},
        …
        </script>
        </body>
        </html>
    "
    возврат HTML_Карта
; 

Обратите внимание на %{...} прямо внутри CSS и JS. Цвет маркера берётся из моей БиблиотекаЦветов, координаты центра - из констант приложения, тема карты - из системной цветовой схемы. То есть JS-карта автоматически получает фирменные цвета и стартовую точку конкретного цеха предприятия из типизированного кода, и мне не приходится дублировать эти значения в двух местах. Поменял оттенок в библиотеке -> поменялось и на карте (и вообще везде где этот цвет используется). Магия просто JS сначала является текстом, а потом трансформируется уже в код.

Обратный мост код Элемента -> JavaScript, я сделал через ВызватьМетод (спасибо Элемент, что подарил мне такую возможность):

@ВПроекте
метод ДобавитьМаркер(Широта: Число, Долгота: Число, НазваниеТочки: Строка = "", ТипМаркера: Строка = "to")
    попытка
        Компоненты.Карта.ВызватьМетод("addMarker", [Широта, Долгота, ТипМаркера, НазваниеТочки])
    поймать Исключение: Исключение
    ;
;

Когда мастер добавляет точку в маршрут заказа, я на форме вызываю Компоненты.КартаМаршрута.ДобавитьМаркер(…), и точка появляется на карте. Так же и с центрированием карты: я использую плавающую кнопку в интерфейсе элемента которая своим вызовом вызывает функцию в JS. Здесь язык мне не помог, а честно заставил спуститься в JS. Но я это спрятал за компонентом Карта с типизированными методами, и снаружи оно выглядит как обычный виджет. То самое, за что я ругал Элемент в первой статье, в итоге дало мне полноценную интерактивную карту в приложении. Здесь должны быть аплодисменты. Ну или картинку с пере обувкой, кому как)) Хотя если думать в корне это костыль, но приятный))

С таймером тот же прикол. В элемент попросту не получится никак реализовать таймер. Ну только если сделать обработчик по таймеру, который каждую секунду будет актуализировать время разницы между текущим временем и временем конкретного статуса, но это дичь. Поэтому я и сделал на JS таймер, которому при каждом обновлении страницы прилетает цифра текущей позиции, и он продолжает отсчитывать спокойно себе на фронте не зависимо от сервера. А при перезапуске также всё актуализирует и продолжит. Удобно))

Ещё хочу предупредить и напомнить: Не забывайте, что при работе с JS в Элементе, вы передаёте строкой поэтому все кавычки нужно экранировать \" и комментарии должны быть закрывающими (многострочными) /* КОД */. Иначе вам посыпятся такие инородные ошибки…

Глава 9. Генерация UI кодом - вот где всё сложилось

Ну и ещё вкуснятина. Больших сложных форм я почти не рисовал мышкой, да и не получилось бы динамическая подгрузка данных. Но хоть вы и видели уже, что формы делаются через yaml, Интерфейс я генерирую кодом xbsl, и вместе с компонентной моделью это дало невероятный уровень гибкости.

Список из данных одним присваиванием

Ключевой приём по всему проекту: у группы есть свойство Содержимое, которому можно присвоить массив компонентов, созданных в рантайме. Вот так формируется список заказов:

метод СозданиеОбновлениеКомпонентаЗаказов(РазделПереключателя: Строка = "", ВсеВодители: Булево = Ложь)
  пер КомпонентыЗаказа: Массив<Компонент>
  для Заказ из ВсеЗаказы
    пер СтатусЗаказа = ПолучитьСтатусЗаказа(Заказ.Ссылка)
    если РасчетСтатусовПереключателя(СтатусЗаказа) == РазделПереключателя
      пер КомпонентЗаказ = новый КомпонентЗаказа()
      КомпонентЗаказ.ПриИзмененииСтатуса = &ПриИзмененииСтатуса
      КомпонентЗаказ.Статус = СтатусЗаказа
      КомпонентЗаказ.Заказ = Заказ
      КомпонентыЗаказа.Добавить(КомпонентЗаказ)
    ;
  ;
  Компоненты.ГруппаКомпановки.Содержимое = КомпонентыЗаказа
;

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

Компонент заказа внутри себя так же генерирует дочерние компоненты точек маршрута и так можно собирать такую матрёшку такой вложенности… Просто ого-го...

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

Местами я собираю форму вообще без разметки, полностью в коде. Вот показ грузов точки - форма создаётся, наполняется и открывается прямо в обработчике нажатия без всяких yaml-файлов:

метод ПолучитьИнформациюОГрузеПриНажатии(Источник: Кнопка, Событие: СобытиеПриНажатии)
  пер КомпонентыГруза: Массив<Компонент>
  для Груз из Точка.ПеревозимыйГруз
    пер КомпонентПредставленияГруза = новый КомпонентПредставленияГруза()
    КомпонентПредставленияГруза.Груз = Груз
    КомпонентПредставленияГруза.Редактирование = Ложь
    КомпонентыГруза.Добавить(КомпонентПредставленияГруза)
  ;
  знч ФормаГруза = новый Форма()
  ФормаГруза.Содержимое = новый ПроизвольныйШаблонФормы(Содержимое = новый Группа(Содержимое = КомпонентыГруза))
  ФормаГруза.ОткрытьВМодальномОкне()
;

новый Форма(), новый Группа(...) - вся иерархия интерфейса строится как обычные объекты. Форма для меня стала таким же значением, которое можно собрать и открыть, а не статичным артефактом из конфигуратора.

И тоже самое мы можем проворачивать и в контрактах из главы 4 работают и для UI. Подсистема Перевозки реализует контракт и отдаёт кнопку как объект тому, кто её попросит:

А форма настроек собирает все такие кнопки со всех подсистем, не зная, кто их прислал:

@НаКлиенте
метод КомпановкаНастроекИспользуемыхФункциональностей()
  пер КомпонентыКнопок: Массив<Компонент>
  для ИспользуемаяФункциональность из КонтрактИспользуемаяФункциональность.ПолучитьСервисы()
    КомпонентыКнопок.Добавить(ИспользуемаяФункциональность.ФункциональнаяОпцияКнопка())
  ;
  Компоненты.КомпановкаНастроекИспользуемыхФункциональностей.Содержимое = КомпонентыКнопок
;

Любая подсистема может подкинуть свой UI-элемент в центральную форму, не создавая зависимостей. Комбинация "интерфейс как объекты" и "внедрение зависимостей через контракты" даёт то, что через типовые механизмы сделать очень тяжело, даже невозможно.

Короче собрать можно всё что хочешь, хоть всё приложение сгенерировать целиком в одном XBSL файле. Как я услышал на днях:

1С:Элемент это вообще не 1С - здесь можно пойти любой дорогой и нет строгих правил и ограничений, можно решить любую задачу совершенно по разному.

Как обычно просто спрашивают:

А это можно сделать? А это?

Можно всё что хочешь и сбоку бантик - вопрос во времени, а так можно всё!

Но, есть но… Да не всё так идеально при таком подходе элемент так пытается торопиться, что забегает слишком далеко вперёд и тем самым что-то может не прорисоваться из-за краха в мозгах. И поэтому я оставлю для вас стыдный лайфхак здесь:

статический метод ВызовПаузы()
    Пауза(100мс)
;

Плак-плак… Я создал монстра, а это его сдерживающая сила...

Вообще с UI очень много проблем, это прям то место, где бывает очень больно. Простой пример: Хочешь у кнопки поменять размер (у неё есть такое свойство Ширина, Высота), но меняешь на 10 ничего, меняешь на 100 ничего, меняешь на 1000 ничего. Даже не колышется и таких мест в свойствах компонентов очень много. По этому эта ещё одна причина залезть в JS((( Грустно, ну а что делать, хочешь красивую приложуху придётся мучится в 1С:Элемент здесь же в названии есть слово 1С... Ну ладно о боли позже...

Должен отметить: чтобы всё это не превратилось в кашу, повторяющийся UI я вынес в компоненты со своим API: ОсновноеСообщение (плашка уведомления), Переключатель (табы "Активные / Выполненные / Отменённые" со счётчиками), ШапкаПрофиля, ГруппаФильтраОтДо и тд. Каждый - виджет со своими свойствами и событиями.

А чтобы генерируемый интерфейс выглядел единообразно, цвета я нигде не хардкодил. Всё идёт через мою библиотеку цветов как я уже отмечал ранее. Модуль отдаёт цвет в двух видах: как АбсолютныйЦвет для нативных компонентов, как строку rgb(...) для HTML-карты и для SVG-иконок (который кстати так же динамически у меня генерируются). Один источник - два потребителя. Плюс адаптив: внутри компонентов через выбор КлиентскоеУстройство.ВидИнтерфейса (Компьютер / Планшет / Телефон) перестраиваются шрифты и компоновка. Одна кодовая база даёт три вида интерфейса. И даже про прозрачность цветов не забыл))

Глава 10. Боль

И её здесь много.

Хочешь настраивать основной цвет приложения из кода динамически - нельзя! На тебе по рукам. Только задать цвет раз и на всегда. И ничего больше не выдумывай...

Есть глобальная область видимости @Глобально

Читаем из документации. Там же не врут...
Читаем из документации. Там же не врут...

Область видимости при которой та или иная сущность видна из других проектов. Ну и ничего подобного. Работает только если мы делаем библиотеку на Элементе, а если просто в одном проекте хотим что-то переиспользовать в другом то ДОСВИДАНИЯ.

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

И отдельная боль это лаги. Тут прям сразу скажу по минимуму делайте, что-то через свойства, хоть это и сделано для упрощения жизни, то на практике совершенно не так. Пишешь быстро значение в свойство Значение и оно начинает не успевать за написание и в какой-то момент ты наблюдаешь картину как третья буква слова начинает зациклено мигать не давая тебе ничего дальше написать. И это не прекращается. По итогу идём в yaml-файл зачищаем и пишем заново уже в нём, ну или пишем каждую букву с интервалом в 3 секунды и мольбой что бы Элемент не умер от твоих очумелых пальчиков.

Можно ещё много чего отмечать... Но для этого скорее нужна отдельная статья. По этому суть сложности написания большого проекта на элементе с нуля я думаю вы уловили...

Заключение. Что в итоге вышло и стоило ли оно того

И что мы имеем? titransflow - теперь реальный продукт на 1С:Элементе.

Два месяца. Один разработчик, один архитектор. На выходе - рабочая система с ролями, машиной состояний, интеграцией по HTTP, push-уведомлениями, картой и мобильным клиентом и не считая продуманный UI (из гов… и палок, извините...). На чистом стеке (React + Node + база + отдельно мобилка) я бы этот же объём делал ощутимо дольше и с большим количеством ручной склейки.

Фото на момент разработки и тестирования (версия на начало июля). Вот тут у нас долгая загрузка переработал логику...))
Фото на момент разработки и тестирования (версия на начало июля). Вот тут у нас долгая загрузка переработал логику...))

Что реально понравилось за время проекта:

  • Единый код клиента и сервера без ручного REST избавляет от целого класса рутины.

  • DI через контракты оказался взрослее, чем я ждал от 1С.

  • Строгая типизация и null-safety ловят ошибки до запуска, а не в цеху у водителя.

  • Встроенный SQL с проверкой на компиляции - это удобно.

Что бесило:

  • В основном всё так же остаюсь верен своему прежнему мнению по поводу многих проблем.

  • Типы иногда разрастаются в монстров на восемь вариантов через палочку. Издержки типизации.

  • Отладка мобильного клиента (особенно push) - отдельный жанр страданий.

  • Штатного UI ой как не всегда хватает, и приходится лезть в КонтейнерHTML.

  • Да и вообще с UI много слёз. Хочешь сделать что бы в Элементе всё выглядело красиво придётся пролить не мало горьких слёз… Вот скажите, что сложно сделать какую-то функцию, возвращающую размер блока/группы/компонента хотя бы не в элементовском абстрактном, а в px… Не могу как ни крути получить размер типового блока(((

Переобулся ли я окончательно? Скажем так: я больше не кручу пальцем у виска на словах "серьёзный проект на 1С (тут в скобочках оставлю ЭЛЕМЕНТ)". Элемент - это не убийца React и не замена Kotlin. Но если у вас внутри компании сложная бизнес-система с ролями, статусами и кучей интеграций, то для таких задач он вполне подходит. Особенно отечественное ПО (где под капотом крутит всё это java, но опустим этот момент). На нём один человек за пару месяцев способен сделать то, на что обычно нужна целая команда. И это факты.

Компьютер по-прежнему понимает только нули и единицы. Просто между ним и мной теперь гораздо больше слоёв - и неизвестно, сколько ещё языков скрыто внутри Элемента. Но при этом всё работает быстро. За это - спасибо…

P.S. Когда я только начал этот проект, у меня в голове крутились одни вопросы: с чего вообще начинать, что делать и как всё это вытянуть. А к финалу я уже дошёл до того, что во сне придумываю решения на обычных языках программирования (python), а потом переношу их на Элемент. Или мы упираемся в стандарты и пытаемся их продавить, или сразу пишем на JS. В комментариях к прошлым статьям Элемент сравнивали с кем угодно. Так вот, покажите мне, на чём вы бы собрали такую же логистику вдвоём за два месяца? Мне правда любопытно.

P.S. На эту статью ушло очень много времени - целых полтора месяца на секундочку (правда параллельно с написанием ещё одной другой статьи, но об этом позже), так что надеюсь вы оцените старания…

Комментарии (35)


  1. Machcnc
    10.08.2026 06:08

    Код на русском языке выглядит... - необычно. Когнитивно трешово. В универе был опыт общения с АЛГОЛ, такой паскаль на русском ))) как-то так и выглядит. Но, если подумать, ничего плохого в этом нет, если будет к примеру Python на русском, то будет прикольно. Или Си++ русская редакция )))) Папа-то может в Си, и какая разница какими буковками он отображается на экране )))


    1. MrSotnik Автор
      10.08.2026 06:08

      Полностью согласен, раньше кривлялся, но по своей сути, что меняется от того на Кириллице язык или на Латинице, важны же другие аспекты


  1. CrushBy
    10.08.2026 06:08

    А в чем конкретный практический смысл, если можно было просто не навайбкодить приложение на любом языке, а с 1С просто через JSON API общаться ?

    Можно конкретные пункты ?


    1. MrSotnik Автор
      10.08.2026 06:08

      Я же уже написал об этом статье. Организациям некоторым не нужны отдельные разработчики под мобилку. На стеке который они не понимают. Им нужно что-то простое и понятное и стой области с которой они знакомы. Да, сперва это не очевидно. Я и сам неоднократно задавался вопросом. Но поработав в таких местах всё становиться на свои месте. Понятные процессы.

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


      1. CrushBy
        10.08.2026 06:08

        Я спрашивал именно с точки зрения бизнеса, а не пространные рассуждения.

        Во-первых, программистов на Kotlin, JS или хз чем в разы больше, чем 1С-элемент (привет аргументам 1С против lsFusion).

        Во-вторых, это боковой локальный интерфейс. Для него вайб-кодинг просто идеально работает. Такое приложение Claude Fable 5 или Opus 5 склепают за пару промптов. И ничего страшного они не поломают, так как там все будет ограничено JSON API на стороне сервера. Тут бизнес сам без проблем сможет его разрабатывать и поддерживать, пока оно очень простое. А когда оно будет сложное, то там и 1С:Элемент уже нихрена не потянет, так как там тупо не хватит возможностей.


        1. MrSotnik Автор
          10.08.2026 06:08

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


          1. CrushBy
            10.08.2026 06:08

            Ну это Вы навязываете им Элемент. Если Вы спросите у них прямо, вот можно на Элемент делать, но это будет ограничено и стоить сотни тысяч рублей, или можете сами фигачить на клоде даже без программистов. Ну или с программистом, а точнее "оператором клода", только это будет стоить несколько тысяч рублей.

            Что же выберет бизнес... Хотя раз уже смогли им втюхать 1С вместо lsFusion, то понятно, что они не сильно современные...


            1. MrSotnik Автор
              10.08.2026 06:08

              Буду душным, но обязан напомнить или объяснить

              Твит автора термина вайб-кодинг
              Твит автора термина вайб-кодинг

              Выделил цитату: "Это не так уж плохо для одноразовых проектов выходного дня, но все равно довольно забавно."

              А то что сделано и продемонстрировано как кейс на Элементе это не проект выходного дня...


              1. CrushBy
                10.08.2026 06:08

                Вы издеваетесь ? Приводить пример цитату от февраля 2025 (!!!!) года. С тех пор ИИ развились на порядок. Вы сами хоть пробовали ими пользоваться ?

                Сейчас Claude уже фактически заменяет всех junior и middle разработчиков. Да, нужен review от senior. Но это в разы меньшие затраты.

                Конкретно для такой задачи он прекрасно подойдет. Но да, такие луддиты, которые еще пользуются 1С, чего-то современного они боятся как огня.


                1. MrSotnik Автор
                  10.08.2026 06:08

                  Прям вижу как прихожу к промышленной компании и говорю, весь ваш 1С ну такое, давайте будем вайбкодить и всё у нас будет супер.

                  И ещё одна оговорочка: Компания меня взяла из-за знаний Элемента потому что нужен был такой специалист. А не потому что я им спамил на почту: - Внедрите Элемент и наймите меня. Рынок конкурентен и для некоторых компаний побеждают именно такие технологии. И возможно эта статья попадёт в робота который собирает данные для обучения Claude Fable 5.1...


                  1. CrushBy
                    10.08.2026 06:08

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

                    Но прогресс не остановишь. Рано или поздно все технологии, которые не подходят под ИИ-агентов вымрут.

                    Прям вижу как прихожу к промышленной компании и говорю, весь ваш 1С ну такое, давайте будем вайбкодить и всё у нас будет супер.

                    Это не так работает. Вы просто даете бизнесу инструмент ИИ. Он сам себе делает программу и деплоит. И у бизнеса просто вау-эффект от результата. Понятно, что так не делают в банках условно, но 95% малого и среднего бизнеса на 95% задач - этого достаточно.

                    Мы так делали на lsFusion. Сейчас Claude со skill'ом (https://github.com/lsfusion/ai-skills) без проблем дорабатывает MyCompany на lsFusion, и деплоит это. Также можно просто с Junie в IDEA. И за этим будущее.


                    1. heiheshang
                      10.08.2026 06:08

                      Что вам мешает то же самое с 1с сделать ? ИИ отлично 1с доробатывает


                      1. Veidt
                        10.08.2026 06:08

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

                        Но даже без этого три очень важные вещи: а) open-source (ИИ может сам посмотреть поведение платформы), б) бесшовное / нативное подключение React / JS / Java / SQL (со всеми накопленными библиотеками / знаниями) в) бесплатность (платить за лицензии платформы, непонятно ради чего, это вообще маразм сейчас).


                      1. heiheshang
                        10.08.2026 06:08

                        Ничего не понял. Я пишу ИИшкой в 1С без проблем, формы рисует, ошибки проверяет, тесты пишет, запросы пишет, расширения, обработки, отчеты. Проверка кода линтером, комиты и пуши в гит. Все то же самое что и везде. Есть mcp сервера, в общем разработка под 1с имеет свою специфику, но с ИИшкой проблем нет


                      1. Veidt
                        10.08.2026 06:08

                        Тут не черно белое. Понятно что ИИкой можно сейчас все что угодно делать, вопрос как быстро, с каким количеством ошибок и токенов, и какого качества решения. Поэтому я и говорил, не линтера, а навернутого линтера.

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


                      1. heiheshang
                        10.08.2026 06:08

                        Компания всю жизнь работает с 1С, как начали с 7.7 так и продолжают, в чем проблема, кто будет людей переучивать на непонятную систему, где вы найдете людей которые работали в чем то другом ? Посмотрите описание вакансий бухов, логистов, менеджеров, везде требование знание 1с. Если у вас уже стоит и работает 1с зачем вам куда-то переходит, уже деньги вложены, оборудование закуплено, люди обучены и работают, у вас вся экосистема заточена под 1с, торговля - бухгалтения - зарплата, как вы вдруг так возьмете и перейдете на что-то другое. Мне как программисту все равно чего они там хотят внедрить, скажите 123 будем вам 123, скажите 321 будет вам 321, когда вы ИИшкой все делаете какая разница какой там линтер, ИИшка мне пайплайн собрала они же его и гоняет


                      1. Veidt
                        10.08.2026 06:08

                        кто будет людей переучивать на непонятную систему, где вы найдете людей которые работали в чем то другом ? Посмотрите описание вакансий бухов, логистов, менеджеров, везде требование знание 1с

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

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

                        Еще раз, ИИка это не черно-белая штука (если вы с ней активно работаете). У нее главная беда, что на неподходящей технологии без линтера ИИ очень быстро растит технический долг и превращается в дикую багогенерилку и токеносъедалку и в конечном итоге затыкается. И также как и в до ИИную эру требует смену платформы / технологии. И благо сейчас количество специалистов уже не так важно, хороший MCP / open-source и интеграция с другими технологиями куда важнее.

                        Но понятно, что если бизнес до этого не дорос, и новых лицензий не надо, то да, зачем дергаться.


                      1. heiheshang
                        10.08.2026 06:08

                        Я не знаю какой у вас опыт внедрения программных продуктов, пользователям как раз не фиолетово, им кнопочку местами поменял и все, затык, работать не могут. Попробуйте человека с айфоном пересесть на андроид, много нового узнаете. Прошло уже 10 лет как компания перешла на 8-ку, а я до сих пор слушаю как было хорошо в 7-ке.

                        У нее главная беда, что на неподходящей технологии без линтера ИИ очень быстро растит технический долг и превращается в дикую багогенерилку и токеносъедалку и в конечном итоге затыкается. Страно слышать о неподходящей технологии для ИИ. ИИ все равно какая технология это просто стат машина. Я пишу на Go, Rust, 1C и не вижу особой разнице в генерации кода ИИ-шкой. Делайте нормальный харнес и не растите тех долг. Но понятно, что если бизнес до этого не дорос, и новых лицензий не надо, то да, зачем дергаться. тут вообще непонятно, зачем вам вдруг инвестировать в технолгиии если у вас все работает на той что есть вы продолжаете развивать то что хорошо работает.


                      1. Veidt
                        10.08.2026 06:08

                        Я не знаю какой у вас опыт внедрения программных продуктов, пользователям как раз не фиолетово, им кнопочку местами поменял и все, затык, работать не могут. Попробуйте человека с айфоном пересесть на андроид, много нового узнаете. Прошло уже 10 лет как компания перешла на 8-ку, а я до сих пор слушаю как было хорошо в 7-ке.

                        Но при этом весь фокус SAP, 1С и lsFusion - что на них делают кастомизируемые решения (причем часто глубоко, где целые команды работают inhouse или outsource). И поэтому они все друг от друга отличаются куда больше, чем интерфейсы платформ между собой. Поэтому те кто пишет требуется не знаю менеджер со знанием 1С просто идиоты.

                        Страно слышать о неподходящей технологии для ИИ. ИИ все равно какая технология это просто стат машина. Я пишу на Go, Rust, 1C и не вижу особой разнице в генерации кода ИИ-шкой. Делайте нормальный харнес и не растите тех долг.

                        Как харнесс поможет с техдолгом? Вы поймите, что ИИки это просто толпа джунов (ну может даже ближе к мидлам, так как технические ошибки не делают), но архитектурное мышление у них на нуле. И если платформа позволяет ошибаться, ИИ будет ошибаться, пока не сломает все нафиг. А платформа как раз определяет, когда все заклинит. Если (бы) у вас был большой опыт разработки командами джунов или ИИ кодирования без review, я думаю вы (бы) это увидели своими глазами.

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

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


                      1. heiheshang
                        10.08.2026 06:08

                        Как харнесс поможет с техдолгом? Вы поймите, что ИИки это просто толпа джунов (ну может даже ближе к мидлам, так как технические ошибки не делают), но архитектурное мышление у них на нуле. Харнес вам как раз и задает рамки чтобы тех долг не разрастался. Без скилов, агентов, mcp, как ИИшка писать будет ? Как она узнает про архитектуру ? Что можно что нельзя, если вы ей не скажите четко она и не напишет четко. Это вообще универсальный вопрос мироздания. И ответ на него “эволюционно-революционный” механизм развития, существующий тысячелетиями - эволюция со временем заходит в тупик (привет техдолгу), после чего нужны революционные изменения (с повышением уровней абстракций и базы решений). Только в ИТ таких революций было 4 где-то крупных (и под десяток мелких). Ну то есть в lsFusion тех долга нет, а в 1С он есть и если поменяем то счастливо заживем ? 1С тех долг копит и ни как с ним не работает ? С 1С вам зачем вообще программисты ? Я редко вижу чтобы в 1С продуктах у мелких предприятий что то надо было постоянно переписывать и разрабатывать, там вообще программисты не нужны, нужны грамотные внедренцы. Я вижу другое, что недалекие программисты на каждый чих пользователя начинают писать код и дублируют типовой функционал только потому-что не разбираются в том с чем работают.


                      1. Veidt
                        10.08.2026 06:08

                        Харнес вам как раз и задает рамки чтобы тех долг не разрастался. Без скилов, агентов, mcp, как ИИшка писать будет ? Как она узнает про архитектуру ? Что можно что нельзя, если вы ей не скажите четко она и не напишет четко.

                        Скилы, агенты, mcp это больше чтобы ИИка не блуждала как ежик в тумане и не сжирала тонны токенов и времени в процессе работы (то есть вопрос экономии, а не техдолга). ИИки хорошо как раз умеют мимикрировать (и кстати rules из-за размывания внимания не так сильно уважают) в этом их смысл, но принимать / оценивать архитектурные решения они физически не умеют. То есть это один в один команда джунов (мидлов). Вы всерьез думаете, что если джунам дать правила, то они перестают накапливать технический долг? Да и что там за правила будут - не дублируй абстракции, не переизобретай велосипед, следи за производительностью? у них это и так есть, что не мешает им ошибаться через раз.

                        Ну то есть в lsFusion тех долга нет, а в 1С он есть и если поменяем то счастливо заживем ? 1С тех долг копит и ни как с ним не работает ?

                        В lsFusion за счет более высокоуровневости / декларативности - в частности реактивности (вот эти все ограничения, события, материализации) ну и модульности (наследования в частности) ИИка изначально в более узких рамках и растит техдолг гораздо медленнее и соответственно позволяет строить куда более сложные и производительные системы. 1С же императивен по сути (одни запросы в строках чего стоят и никаких реактивностей как класса нет) и дает возможность выстрелить себе в ногу на каждом шагу. Да, раньше была проблема, что людей понимающих (и хотящих разбираться с новой парадигмой / языком) сложнее найти, но сейчас во времена ИИ это не проблема. ИИка не будет ныть что я не хочу, а главное что она явно проецируют свои знания из условных Lisp, SQL, React и т.п. и отлично умеет писать на lsFusion (ну а дальше навернутый линтер, mcp, вот это все)

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

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


                      1. Necessitudo
                        10.08.2026 06:08

                        Какая связь у 1С: Элемента и 1С?


                      1. MrSotnik Автор
                        10.08.2026 06:08

                        По своей сути никакой, только название


                      1. CrushBy
                        10.08.2026 06:08

                        Во-первых, 1С - это коммерческий продукт. Непонятно нахрена он нужен, если есть открытый lsFusion. С нормальной DOM-моделью для браузера, с которым прекрасно работает Claude с помощью CSS и JS.

                        Во-вторых, он хреновый с технологической точки зрения. И ладно бы программистам 1С. Они то уже привыкли. А вот ИИ от всяких костылей в архитектуре, как у 1С, часто крышу сносит.

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

                        В том же lsFusion есть как официальный общий MCP (https://ai.lsfusion.org/mcp), в MCP-сервер самой IDEA добавляются методы для работы с lsFusion, а также в каждом сервере есть встроенный.

                        Вообще попросил ChatGPT поискать примеры результатов использования 1С. Из более менее законченного он нашел только вот это : https://infostart.ru/1c/articles/2618356/

                        Для сравнения только что дал промпт для lsFusion :

                        Промпт

                        Create a modern, responsive web application for an HR and payroll department. It should manage employees, attendance, leave, payroll calculations, and payslips in one integrated system.

                        Core functionality:

                        1. Dashboard

                        • Show active employees, new hires, absences, pending approvals, payroll status, upcoming payments, and expiring contracts.

                        • Include charts for headcount, payroll costs, overtime, and absences.

                        • Allow filtering by company, department, location, and period.

                        1. Employee Management

                        • Store personal details, position, department, manager, contract, salary, working schedule, bank account, tax information, and employment history.

                        • Attach contracts, certificates, medical examinations, and other documents.

                        • Provide employee search, filters, organizational structure, and contract-expiration reminders.

                        1. Time and Leave

                        • Record working hours, overtime, night hours, weekends, holidays, business trips, remote work, and absences.

                        • Allow employees to submit leave requests and managers to approve them.

                        • Automatically calculate vacation balances, payable working days, overtime, and absence duration.

                        • Provide calendar and monthly timesheet views.

                        1. Payroll Calculations

                        • Create monthly payroll runs for selected companies, departments, or employees.

                        • Calculate gross and net salary using configurable tax and social-contribution rules.

                        • Support monthly and hourly salaries, prorated salary, overtime, bonuses, commissions, allowances, sick pay, vacation pay, unpaid leave, deductions, advances, and corrections.

                        • Show a detailed calculation breakdown with formulas, rates, quantities, and amounts.

                        • Allow HR specialists to preview, recalculate, verify, approve, lock, and reopen payroll periods.

                        • Compare the current payroll with previous periods and highlight unusual differences.

                        • Calculate total employer cost and generate payment and accounting summaries.

                        1. Payslips

                        • Generate individual payslips containing earnings, bonuses, overtime, taxes, contributions, deductions, employer costs, and the final net payment.

                        • Allow employees to securely view and download their payslips as PDF files.

                        • Support bulk generation and email distribution.

                        • Keep a complete payslip history and regenerate corrected versions without losing previous versions.

                        1. Reports and Exports

                        • Provide reports for payroll costs, taxes, contributions, attendance, overtime, absences, and vacation balances.

                        • Allow filtering, grouping, and exporting data to Excel, CSV, PDF, accounting systems, and banking systems.

                        User roles:

                        • HR Administrator: full access to employees, payroll, settings, and reports.

                        • Payroll Specialist: access to calculations, payslips, payments, and payroll reports.

                        • Manager: access to team information, timesheets, and approvals.

                        • Employee: access to their own profile, leave requests, timesheets, and payslips.

                        • System Administrator: technical configuration without access to confidential salary information.

                        Interface requirements:

                        • Use a professional layout with sidebar navigation, global search, notifications, tables, filters, bulk actions, and clear status indicators.

                        • Provide detailed employee, timesheet, payroll-run, and payslip pages.

                        • Make calculation results transparent and easy to verify.

                        • Support desktop, tablet, and mobile devices.

                        • Include realistic sample data and completed payroll examples.

                        Implement role-based permissions, audit history, configurable calculation rules, approval workflows, automatic reminders, and protection of sensitive personal and salary data.

                        И он сразу с нуля сгенерировал приложение с заполненными тестовыми данными и 30 формами.

                        1С так может ? Можно примеры ?


    1. Nikollor48
      10.08.2026 06:08

      Бизнесу тупо проще держать одного спеца, который закроет весь контур от базы до интерфейса водителя. Зоопарк из JSON API, отдельной мобилки и бэка требует уже трех разных людей, которые обязательно переругаются при первом падении прода


      1. CrushBy
        10.08.2026 06:08

        Специалист по бэку тут зачем ? А на мобиле и 1С специалист с клодом прекрасно справится. ИИ хреново умеет в 1С, а вот в простую мобилу он отлично умеет. 1С-специалисту нужно будет только промпты генерить и все. Дополнительные спецы не потребуется.

        Если же речь идет о разработке без 1С, то есть такое понятие Full Stack Developer. Он умеет во всё сразу.

        А в современных реалиях достаточно одного Team Lead и Claude, чтобы разработать все, что угодно. На базе node.js или lsFusion - это не принципиально.


  1. Naf2000
    10.08.2026 06:08

    возврат РезультатЗапроса.ЕдинственныйИлиНеопределено()?.Статус ?? ВидыСтатусовТочекЗаказов.Новая

    ЕдинственныйИлиНеопределено() вернёт либо строку результата, либо Неопределено?.Статус безопасно достаёт поле, а ?? подставляет значение по умолчанию, если записи не нашлось. Одна строка вместо блока с вложенными проверками. Заодно обратите внимание, что метод имеет тип возвращаемого значения - ВидыСтатусовТочекЗаказов

    Важно отличать от таких же методов с префиксом Первый (ПервыйИлиНеопределено, ПервыйИлиУмолчание).

    Если Статус перенести в запрос, то код еще более упростится до

    возврат РезультатЗапроса.ЕдинственныйИлиУмолчание(ВидыСтатусовТочекЗаказов.Новая)


    1. MrSotnik Автор
      10.08.2026 06:08

      Тут скорее код для демонстрации нежели истинно верный))


  1. Naf2000
    10.08.2026 06:08

    Язык запросов живёт прямо в коде, и это не строка, а типизированный литерал с проверкой на этапе компиляции

    Это прекрасно, это практически LINQ to SQL в мире .Net. А переиспользовать Запрос можно? Например, функция возвращает запрос (не результат, а именно запрос) выборки заказов со статусом "К отгрузке". А мы хотим дополнить его дополнительными фильтрами, например отбором по складу?


    1. MrSotnik Автор
      10.08.2026 06:08

      В теории можно, но я так не пробовал)) Как минимум можно передавать строку с запросом, а потом её досоздавать по надобности через код строкой и выполнять


      1. Naf2000
        10.08.2026 06:08

        код строкой - такого добра в обычном 1С полно. Весь цимус в корректном написании кода, который можно проверить в compile-time, а не когда упадет в run-time.


        1. Veidt
          10.08.2026 06:08

          Именно, это очень удобно для ИИк, собственно это было понятно с самого начала, именно так делал и LINQ и SAP и lsFusion. И до 1С наконец дошло.

          Но в современном мире ИИ ИМХО все платное и не опен-сорс (чтобы ИИ мог туда подглядывать) умрет в скором времени. Учитывая что главный аргумент как 100к разработчиков в мире ИИ уже мало чего значит.


  1. Nikollor48
    10.08.2026 06:08

    За идею с контрактами и DI прямо из коробки разработчикам платформы реально плюс


  1. TIEugene
    10.08.2026 06:08

    ВыгрузкаПоДебиторскойЗадолженностиИПоЗадолженностиОтПосредниковРаботающихСУдержаниемКомиссииГрафикБудущихПлатежей

    Sorry, не удержался.


  1. KoIIIku_PuJI9IT
    10.08.2026 06:08

    сейчас из себя представляет эта технология на нашей родной КИРИЛЛИЦЕ

    Нет такого слова на твоей родной кириллице.

    Русский мне родной.

    Зато можно корчить из себя разные мертворожденные элементы ...