Заметки: 1С

Мои мысли о жизни, работе и разных интересных штуках вокруг меня.

Показано: 0 · Страница

Ду ю спик инглиш?
14 мая 2023 ·

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

Диалог входа

Слышал, что развитие Конфигуратора остановилось во многом из-за чудовищного количества технического долга, тормозящего любые новые фичи. Но тут интерфейс поехал прямо на одном из первых окон приложения! Любопытно, как это пролезло через тесты.

Может, их и вовсе нет.

Пауза()
30 апреля 2023 ·

Важная особенность: метод ВызватьПаузу недоступен в клиент-серверном вызове; при вызове с клиента серверного метода, в котором вызывается ВызватьПаузу, будет сгенерировано исключение «Нельзя вызвать метод ВызватьПаузу в клиент-серверном вызове».

Метод ВызватьПаузу

Странное ограничение, если честно. С одной стороны, адекватный разработчик и так не будет в клиент-серверном вызове делать искусственную паузу, с другой — кому чешется, всё равно её там сделает (через проверку времени в цикле, например). Стоило ради security by obscurity огород городить?

В лучшем случае какой-нибудь джун схватит это исключение, подумает "похоже, я что-то не так делаю" и двинется в правильном направлении. Однако закладывать в платформу ограничение ради этого кейса — как из пушки по воробьям палить. Давайте ещё лимит по количеству прикрутим — мол, не больше 1000 пауз на сеанс, а то вдруг пользователи подумают, что программа работает слишком медленно :-)

Сделай это педантично
21 марта 2023 ·

Ежедневная пальма первенства в номинации «самый философский код» уходит автору этого элегантного способа проверить, что две булевые переменные не равны друг другу:

If DataStructure.Property("AmountVATIn")
    And ((DataStructure.AmountVATIn And NOT SearchPriceIncludesVAT)
    OR (NOT DataStructure.AmountVATIn And SearchPriceIncludesVAT)) Then    
    Price = RecalculateAmountOnVATFlagsChange(Price, DataStructure.AmountVATIn, TabSectionLine.VATRate);
EndIf;

Думаю дописать сюда что-нибудь вроде «And Not (DataStructure.AmountVATIn = SearchPriceIncludesVAT)», чтобы придать происходящему тонкую нотку безумия.

Помедленнее, я записываю
5 марта 2023 ·

Обычно при разработке исходишь из простой формулы: чем быстрее работает — тем лучше. Например, чем больше запросов приложение успеет выполнить за единицу времени — тем быстрее решится задача, ради которой эти запросы нужны.

Однако бывает наоборот: нужно снизить количество операций, которые программа способна выполнить. Допустим, мы дергаем внешний сервис и он банит, если делать это слишком часто. Пример — облако Битрикс24, которое требует отправлять не больше двух запросов в секунду.

Вот реализация такой замедлялки, которую я написал на прошлой неделе. Поддержки очередности в ней нет; основная решенная задача — выполнить как можно больше запросов, не выходя за лимит (с учётом того, что запросы могут делаться из разных сеансов).

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

REST-сервис для Service Manager
25 февраля 2023 ·

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

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

Эта — хороший пример. Настройка сервис-менеджера не была проблемой (запустить приложение и покрутить настройки), но о ней нужно было думать и её нужно было делать. А теперь — нет:

  1. Есть JSON-файл со всеми настройками;
  2. Есть REST-сервис для его обработки;
  3. Есть скрипт, который засунет первый во второй;
  4. Есть пайплайн, который сделает всё это сам.

Короче, ботинок тёр ногу, а теперь перестал. Йаху!

Карьерные самосвалы
20 февраля 2023 ·

Автомобили

Коллега проворчал, что если продолжать эту логическую цепочку, то конфигуратор будет «Жигулями», а EDT — «Кама1» (это такой электрокар, который много лет пилят где-то в недрах КАМАЗа и всё никак не могут собраться с силами и, наконец, допилить).

Ну а я стараюсь смотреть на вещи с оптимизмом. Думаю, платформа и её IDE — это такие карьерные самосвалы. Никто в здравом уме на них по домашним делам не катает, но на разрезе эти звери незаменимы!

Взболтать, но не смешивать
7 августа 2022 ·

Затрем немного про организацию кода. Если вам нужно где-то описать множество объектов с общими свойствами — подумайте, не стоит ли подробить описание на отдельные методы, каждый из которых будет описывать один конкретный объект?

Посмотрим на пример — метод, который описывает табличные части документов, пригодные для какой-то задачи. Что-то вроде:

SupportedTypes["Document.SupplierPricesEntering"]   = "Prices";
SupportedTypes["Document.OpeningBalancesEntering"]  = "CustomerAccounts,VendorAccounts";
SupportedTypes["Document.Requisition"]              = "InventoryAndServices";

Вроде всё в порядке, так? Описания есть, табличные части перечислены, посплитить их по запятой — считай, бесплатно.

Однако документов в методе описано много. Рано или поздно какому-нибудь коллеге (или вам самим) понадобится добавить ещё одну табличную часть для документа, который уже описан в методе. Его что-то отвлечет, он забудет поискать существующую строку и получится что-то вроде:

SupportedTypes["Document.SupplierPricesEntering"]   = "Prices";
SupportedTypes["Document.OpeningBalancesEntering"]  = "CustomerAccounts,VendorAccounts";
SupportedTypes["Document.Requisition"]              = "InventoryAndServices";

<...>

SupportedTypes["Document.OpeningBalancesEntering"]  = "PayrollDeductions";

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

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

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

Procedure AddOpeningBalancesEnteringDocument(SupportedTypes)

    TabularSections = New Array;

    TabularSections.Add("CustomerAccounts");
    TabularSections.Add("VendorAccounts");
    TabularSections.Add("PayrollDeductions");

    SupportedTypes["Document.Requisition"] = StrConcat(TabularSections, ",");

EndProcedure

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

Смотри не перепутай
16 марта 2022 ·

Код

Если вид операции — продажа товара или недвижимости, то открой общую форму AdvancesPickFormWithVAT с параметрами, описанными в структуре PickParameters. Колбеком будет метод EditPrepaymentOffsetEnd, описанный в этом же модуле; передай ему структуру AdditionalParameters. Форму нужно открыть так, чтобы она заблокировала весь интерфейс.

А вот если вид операции — возврат поставщику, то открой общую форму AdvancesPickFormWithVAT с параметрами, описанными в структуре PickParameters. Колбеком будет метод EditPrepaymentOffsetEnd, описанный в этом же модуле; передай ему структуру AdditionalParameters. Форму нужно открыть так, чтобы она заблокировала весь интерфейс.

Смотри не перепутай.

Переиспользуйте с осторожностью
8 февраля 2022 ·

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

Начинаю расследовать. Первым делом смотрю на скрины в Аллюре: о'кей, причина очевидна — в одном из документов сдохло условное оформление для поля с суммой НДС. Тест ожидал, что оно будет недоступно, если ставка НДС равна нулю, а оно оказалось доступным.

Непорядок, надо чинить. Смотрю на условие оформления в коде: ну, поле блокируется, если ссылка на ставку НДС есть в списке «нулевых» ставок (т.е. ставка в которых равна нулю). Всё просто и логично. Что тут, блин, могло сломаться?

Твит

Ладно, лезу в документ ручками. А там внезапно всё тип-топ: оформление работает как надо. Плавающий баг, что ли? Запускаю автотест снова, в нужный момент вклиниваюсь с отладчиком и обнаруживаю какую-то откровенную фигню: в списке «нулевых» ставок, кроме них самих — пачка пустых ссылок!

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

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

Как? Ну, если вы тянете из общего модуля со включенным кэшированием какие-то значения и они не примитивного типа (строка, число и т.п.) — вы получите не само значение, а указатель на него где-то в памяти. Запишете этот указатель в переменную и попробуете поменять — поменяете кэш.

Вот так просто, да. Это и произошло в моем случае: метод, формирующий список «нулевых» ставок, вызывала форма другого документа. Получив список значений, она непринужденно дописывала к нему пустую ссылку и использовала в своей логике. Таким образом, при каждом открытии этой формы кэш списка прирастал всё новыми и новыми пустыми ссылками, что в конце концов сломало документ на другом конце конфы.

Твит

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

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

Infostart Event 2021
16 ноября 2021 ·

На прошлой неделе сгонял в Москву на Infostart Event 2021:

  • Послушать дюжину докладов о разработке и близких темах — check;
  • Развиртуализоваться с несколькими классными людьми — check;
  • Поболтать с друзьями — check!

По итогу неожиданно почуствовал себя отдохнувшим — и это несмотря на перелёты, нервы и суету. Думаю, дело в том, что я удалёнщик; было по-настоящему здорово посмотреть на такое количество коллег вживую, а не в виде потока электронов.

Чтобы два раза не вставать летать, сдал в офисе PostgresPro входной тест по администрированию PostgreSQL. Впереди ещё три, а финального пока вообще не существует — но дорогу осилит идущий, так вижу.

Матрёшка
30 октября 2021 ·

На днях в чат команды кинули ссылку на сертификат 1Ci (коллега проходил их курс на джуна). Перехожу по ней — авторизации не требует, ошибки не выдает, браузер сразу загружает PDF. Всё идёт как надо, верно? Ладно, открываю файл:

Error.pdf

Честно? Я даже восхищён. Надо этот UX в наших продуктах внедрить: прожимаешь ты, например, печать документа, а он тебе — PDF! А в нём — ссылка на другой PDF! А в нём — entity is not filled, иди заполняй.

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

Самые большие зарплаты
2 октября 2021 ·

Есть путь до CSV-файла, надо его открыть, прочитать заголовок (первая строка), найти колонку Salary и вывести топ 10 зарплат.

Ссылка

Вбросил свои пять копеек просто для полноты картины. Если забить на стабильность, читаемость и производительность — можно усечь где-то вдвое. Тут с самого начала было очевидно, что на баше выйдет короче, а на питоне проще — так что просто написал как привык.

Из полезного: в комментариях к сообщению куча примеров на других языках. С некоторыми вообще до сих пор не сталкивался; было реально любопытно посмотреть на синтаксис и попробовать понять подход.

В целом история напомнила хобби Евгения Степанищева — писать вывод американской «песни о пиве» на всех языках подряд. Задача с чтением CSV, кстати, мне тоже кажется скорее фаном — слишком узкий кейс, чтобы на его базе что-то всерьёз сравнивать.

Из смешного: у пары коллег язык 1С вызвал настолько острые проблемы со зрением, что они сочли нужным об этом сообщить :-) Отчасти понимаю желание самоутвердиться на стереотипе «1С — это плохо, понятненько?», но тут момент явно выбран неудачно. Предпочтения в синтаксисе — дело вкуса, а кроме них решение на 1С никак не отличается от решений на любом другом языке, где нет встроенной библиотеки для парсинга CSV.

Про молоток и гвозди
15 сентября 2021 ·

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

Вот есть у вас документ, да? В нём — несколько табличных частей. В каждой — поле для комментария. Делаете вы для этого документа печатную форму; если хотя бы в одной строке любой ТЧ есть комментарий — нужно использовать один шаблон, если комментариев нет — другой.

Задача примитивная, все мы миллион раз такое делали — ну, смотрим в выборку строк, натравливаем ПустаяСтрока() на нужное поле и грузим подходящий шаблон. Готово, можно пить кофе!

Однако, вместо короткого цикла я увидел это:

Ну то есть да, делаем матрешку из подзапросов, в самом нижнем из которых роемся в ТЧ (которые мы, напомню, только что выгребали для вывода на печатную форму). Ищем в них комментарии, если есть — ставим единичку. Нет? Нолик. Итог несколько раз группируем и возвращаем в скрипт.

Я сейчас даже не про нагрузку на СУБД (рискну предположить, что заметного эффекта этот трюк не дает — в конце концов, после отбора по ссылке выборка будет копеечной). Просто… Ну… Чекнуть выборку строк — пять строк кода. Понятных, простых, коротких, Сонару ругнуться негде. Как можно было родить вот это? Из большой любви к запросам?

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

Без комментариев
8 сентября 2021 ·

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

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

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

Медленное оповещение
18 августа 2021 ·

Твит

Прочитал этот твит и внезапно вспомнил случай из практики. Прилетает запрос от клиента — мол, 1С тормозит, сделайте что-нибудь. Уточняем: тормозит всё-таки не вся 1С, а конкретная операция.

База клиента — переписанная УТ, проблемная операция — из блока доработок. Внутри — множество многоэтажных запросов, возня с таблицами значений, какие-то невнятные вычисления и пачка серверных процедур по семьсот строк каждая. Копию базы снять нельзя, ТЖ недоступен, доступ к СУБД закрыт, доступ к счётчикам — тоже. Отладка на сервере 1С выключена, включить — не вариант.

Бардак, в общем. Как понять, где тут всё вязнет? Ладно, конфигурацию можно менять — уже неплохо. Вешаю на операцию замер: ну да, думает над чем-то пару минут. Отлично! Потираю ручки и сужаю область поиска: докидываю целый ворох замеров на самые подозрительные участки.

Результат любопытный: тормозов нет! Код не то чтобы летает, но о минутах и речи нет — с учетом сложности, расходы в пределах допустимого. Может, какая-то сериализация данных между клиентом и сервером? Тщательно выверяю код серверной процедуры — нет, никакой трансляции. Всё, что было на сервере, осталось на сервере.

Перехожу к клиентской части. Что тут может тормозить-то?.. Выглядел код, упрощая, как-то так:

Смотрел я на этот код, как баран, минут десять. В конце концов спросил коллегу и он рассказал удивительную вещь: оказывается, Оповестить() не просто рассылает оповещения во все открытые формы, а ждёт, когда в них завершится обработчик оповещения.

Я знатно офигел, полез проверять и в конце концов наткнулся на форму, которая ловила событие выполнение операции и подрывалась обновлять свои динамические списки. Запрос в одном из них и давал тормоза (там подзапрос к подзапросу к подзапросу и ворох соединений «через точку» — в общем, дальше не интересно).

Такая вот яломиште :-) Оповещайте с осторожностью!

Умер Павел Чистов
1 августа 2021 ·

Павел Чистов в пятницу вечером умер из-за сердечной недостаточности. Если вы не знаете, кто это — вот некролог. Впрочем, раз вы читаете этот блог — скорее всего, знаете.

Никита Грызлов, Илья Леонтьев и Сергей Горшенин собирают деньги для помощи семье Павла. Кроме того, номер карты его вдовы есть в конце некролога по ссылке выше.

Берегите себя, пожалуйста.

Поиск долгих запросов с помощью Python
18 июля 2021 ·

Выложил скрипт на Python для поиска длительных запросов в ТЖ 1С. Накатал его в приступе отчаяния: никак не мог понять, почему мой верный баш для одного из запросов выдаёт среднее время выполнения больше максимального.

Как выяснилось, проблема была в gawk. Для некоторых событий ТЖ эта утилита не могла определить длительность: пыталась преобразовать строку в число, фейлилась и… Нет, что вы! Конечно, не кидалась исключением! Просто невозмутимо считала эти строки за ноль и ехала дальше.

Патч, кстати, вышел ещё глупее проблемы: я просто сделал явное преобразование строки в число, и всё заработало как надо. Чем, блин, явное преобразование в мире gawk'а отличается от неявного? И, главное, почему?

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

Это вполне рабочая тактика, пока входящий поток не переваливает за сотни тысяч элементов: где-то тут мы начинаем терять гигабайты ОЗУ на хранении коллекций и прорву времени процессора на поиске в них. Новый скрипт попрямее: коллекция одна, но хранит всe данные по каждому запросу.

Почему не баш?
3 июля 2021 ·

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

Чтобы не быть голословным, давайте пример из практики. Есть, скажем, порядка семидесяти гигабайт логов ТЖ 1С, по которым нужно построить топ пояснений к исключениям — от самых частотных к менее частотным.

Плёвое дело, верно? Выгребаем EXCP, извлекаем Descr, считаем повторения. Даже нужный скрипт я как-то уже писал. Запускаю, терпеливо жду…

Знаете, сколько понадобилось времени? Я тоже нет: после того, как скрипт проработал сутки, я его вырубил и полез разбираться, в чём проблема. Затык возник где-то в скрипте gawk'а: именно она активно нагружала процессор (если не считать cat'а, которая время от времени читала очередную порцию данных).

Процессы

Беда, беда, огорчение! В общем, я подумал и переписал этот скрипт на Питоне. Новая версия отработала за 15 минут и дала мне:

  1. Топ пояснений по событиям исключений;
  2. Скрипт, который можно прочитать через полгода без помощи гугла;
  3. Уверенность, что я могу добавить в скрипт два-три условия и не вызвать Сатану случайным сочетанием операторов и ключей.

Конечно, я мог оптимизировать версию на баше. Вероятно, тормозит поиск в массиве — время, необходимое для поиска пояснения в массиве уже зафиксированных пояснений, линейно растет с увеличением размера массива. Можно, например, попробовать изменить подход к сбору данных — gawk'ом только извлекать сами пояснения, а результат сбора передать в тандем sort & uniq.

Однако это уже отчётливо отдаёт мемасом про буханку хлеба: из неё, конечно, можно сделать троллейбус, просто не очень понятно — зачем? Камон, мне бы проблему решить. А Питон с ней уже справился на твердую пятёрку, чем сэкономил мне кучу времени и нервов.

Собственно, к этому я вел. Родовые травмы баша понятны и нередко приемлемы, плюсы — приятны и очевидны, но при работе с ним вопрос иногда встает так: cтоит ли только ради того, чтобы получить решение именно на баше, потратить часа два на возню с утилитами, параметрами, мануалами и постами на Stack Overflow?

Твит

Вот поэтому не баш, да.

UPD: Хорошая заметка Ивана Гришаева на ту же тему.

Почему баш?
23 июня 2021 ·

Периодически вижу вопросы коллег: а что, 1С всё ещё носится с башем, да? А чё не пайтон-то? Или павершелл, на худой конец? Вот чудаки!

Да, баш не родной для Windows (которая, напротив, родная для 1С) и притащить его туда — отдельная история; да, при усложнении задачи читаемость скрипта падает по экспоненте; да, с некоторыми задачами баш просто не справляется.

Однако главный плюс баша в том, что для многих задач по анализу ТЖ 1С он — самое простое и быстрое решение. Как старая отвертка, которой ещё дед пользовался. Она всегда под рукой — пусть поцарапанная, с зазубринами и слегка корявая, но всё еще прекрасно работает.

Пример? Допустим, нужно выгрести из ТЖ исключения; простоты ради договоримся, что достаточно первой строки каждого события. Получим примерно такой скрипт на пайтоне.

Скажете, его можно легко сократить вдвое? А то и втрое. В конце концов, мы решаем локальную задачу. Возня с читаемостью и предсказуемостью тут ни к чему.

Согласен! Но давайте сначала решим ту же задачу на баше:

grep -r --include "*.log" ',EXCP,' > result.txt

Вот почему баш.

Метаданные не найдены
8 июня 2021 ·

Метаданные не найдены

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

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

В двух словах о том, как вообще работает история данных. По дефолту она выключена, и достаточно привилегированный пользователь может включить её для нужных ему объектов — констант, справочников, документов и так далее. После этого при изменении данных в этих объектах платформа будет фиксировать, что именно меняется. Делает она это в два этапа: сначала — относительно быстрая запись в промежуточный буфер (таблицу _DataHistoryQueue0); потом, при вызове метода ОбновитьИсторию(), информация мигрирует в основное хранилище версий (таблицу _DataHistoryVersions) и становится доступна в интерфейсе приложения.

Обновление истории можно делать сразу при записи, но для сокращения транзакции его обычно выносят в регламентное задание. В итоге миграция данных из буфера в основную таблицу происходит с некоторой периодичностью: например, раз в пять минут. Так вот, если между изменением данных объекта и обновлением истории данных выключить историю объекта и удалить его версии — метод ОбновитьИсторию() выкинет исключение: он попытается переместить изменения из буфера в основную таблицу, но не сможет привязать их к уже накопленным данным.

Для наглядности — рабочий код, воспроизводящий проблему для справочника Items на платформе 8.3.17.1989. Можете подставить любой другой объект, с которым работает история данных — разницы нет.

Как избежать этой ошибки? Ну, очевидно: метод ОбновитьИсторию() нужно вызывать непосредственно перед тем, как отключать историю для объекта. Лучше всего делать это в период, когда пользователи с системой не работают: есть риск, что между обновлением истории и её выключением вклинятся чьи-то транзакции, которые спровоцируют ту же проблему. Для гарантии можно использовать монопольный режим.

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

Как исправить эту ошибку? Включите историю данных для проблемного объекта, обновите историю и выключите её обратно. Выйдите, так сказать, из класса и зайдите нормально.