Добавление строк и изменение реквизитов табличной части документа в 1С 8.3

· 10 мин

Содержание

Чтобы записать значение в табличную часть 1С, получите объект документа (Ссылка.ПолучитьОбъект() или Документы.Имя.СоздатьДокумент()), добавьте строку методом Объект.Товары.Добавить(), заполните её реквизиты и вызовите Объект.Записать(). Через ссылку табличную часть изменить нельзя: ссылка только читает данные из базы.

Самая частая ошибка новичка — поменять значение в строке и забыть записать объект. Код отрабатывает без ошибок, а в документе ничего не меняется. Вторая ошибка — ждать, что программно добавленная строка сама пересчитает сумму и НДС, как при ручном вводе. Не пересчитает: обработчики формы при работе из кода не срабатывают.

Как устроена табличная часть и где её создают в Конфигураторе

Табличная часть — это вложенная таблица внутри документа, справочника или бизнес-процесса. У документа «Реализация товаров и услуг» это, например, Товары и Услуги. Каждая строка хранит свои реквизиты и служебное поле НомерСтроки, которое платформа нумерует сама.

Состав колонок задают в Конфигураторе:

  1. Откройте дерево конфигурации и дважды щёлкните по документу.
  2. В окне объекта перейдите на вкладку «Данные».
  3. В разделе «Табличные части» добавьте новую табличную часть и задайте ей имя.
  4. Внутри неё добавьте реквизиты и укажите каждому тип: СправочникСсылка.Номенклатура, Число с длиной и точностью, Строка и т. д.
  5. Обновите конфигурацию базы данных клавишей F7.
  6. Чтобы пользователь видел таблицу, откройте форму документа и перетащите табличную часть из списка реквизитов Объект в дерево элементов формы.

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

Точные имена колонок в типовых документах смотрите в Конфигураторе, а не угадывайте. Ошибка в имени даёт исключение Поле объекта не обнаружено (Колво) уже при выполнении кода.

Основные методы табличной части объекта:

МетодЧто делает
Добавить()Добавляет строку в конец, возвращает её
Вставить(Индекс)Вставляет строку на позицию, индексы с 0
Найти(Значение, "Колонка")Первая строка с этим значением или Неопределено
НайтиСтроки(Структура)Массив строк по отбору из нескольких колонок
Удалить(Строка или Индекс)Удаляет строку
Очистить()Удаляет все строки
Выгрузить() / Загрузить(ТЗ)Обмен данными с таблицей значений
Итог("Колонка")Сумма по числовой колонке

Добавление новой строки в табличную часть документа: пример кода

Код ниже выполняется на сервере: в общем модуле, модуле объекта, внешней обработке или в процедуре формы с директивой &НаСервере. Имена документа и колонок взяты для примера, подставьте свои.

Процедура ДобавитьТоварВДокумент(ДокументСсылка, Номенклатура, Количество, Цена) Экспорт

    ДокументОбъект = ДокументСсылка.ПолучитьОбъект();

    НоваяСтрока = ДокументОбъект.Товары.Добавить();
    НоваяСтрока.Номенклатура = Номенклатура;
    НоваяСтрока.Количество   = Количество;
    НоваяСтрока.Цена         = Цена;
    НоваяСтрока.Сумма        = Количество * Цена;

    Если ДокументОбъект.Проведен Тогда
        ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
    Иначе
        ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
    КонецЕсли;

КонецПроцедуры

Режим записи выбирайте осознанно:

  • 📌 РежимЗаписиДокумента.Запись — сохраняет документ без движений. Если документ был проведён, движения останутся старыми, а табличная часть уже новой.
  • ✅ РежимЗаписиДокумента.Проведение — сохраняет и перепроводит, движения регистров совпадут с новыми строками.
  • ⛔ РежимЗаписиДокумента.ОтменаПроведения — снимает проведение и удаляет движения.

Для нового документа порядок тот же, только объект создают через менеджер:

ДокументОбъект = Документы.РеализацияТоваровУслуг.СоздатьДокумент();
ДокументОбъект.Дата = ТекущаяДатаСеанса();
ДокументОбъект.Контрагент = Контрагент;

НоваяСтрока = ДокументОбъект.Товары.Добавить();
НоваяСтрока.Номенклатура = Номенклатура;
НоваяСтрока.Количество   = 2;
НоваяСтрока.Цена         = 1500;
НоваяСтрока.Сумма        = 3000;

ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);

Строку в начало таблицы вставляет ДокументОбъект.Товары.Вставить(0). Номера строк платформа пересчитает сама, заполнять НомерСтроки вручную не нужно.

В типовых документах у строки есть колонки, которые при ручном вводе заполняет форма: ставка и сумма НДС, счета учёта, единица измерения. Из кода их нужно заполнить самому. Если пропустить, документ запишется, но при проведении может отказать или сформировать проводки с пустыми счетами.

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

Поиск и изменение существующих строк табличной части

Изменить значение в уже существующей строке можно, только найдя её. Для одной колонки хватает Найти:

Строка = ДокументОбъект.Товары.Найти(Номенклатура, "Номенклатура");

Если Строка = Неопределено Тогда
    Строка = ДокументОбъект.Товары.Добавить();
    Строка.Номенклатура = Номенклатура;
    Строка.Цена = Цена;
КонецЕсли;

Строка.Количество = Строка.Количество + 1;
Строка.Сумма = Строка.Количество * Строка.Цена;

Проверка на Неопределено обязательна. Без неё при отсутствии товара код упадёт с ошибкой Значение не является значением объектного типа (Количество).

Для отбора по нескольким колонкам нужен НайтиСтроки. Он возвращает массив, даже если найдена одна строка или ни одной:

Отбор = Новый Структура("Номенклатура, Цена", Номенклатура, 1500);
НайденныеСтроки = ДокументОбъект.Товары.НайтиСтроки(Отбор);

Для Каждого СтрокаТЧ Из НайденныеСтроки Цикл
    СтрокаТЧ.Цена  = 1400;
    СтрокаТЧ.Сумма = СтрокаТЧ.Количество * СтрокаТЧ.Цена;
КонецЦикла;

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

ДокументОбъект.Товары.ЗаполнитьЗначения(СтавкаНДС, "СтавкаНДС");

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

ПустыеСтроки = ДокументОбъект.Товары.НайтиСтроки(Новый Структура("Количество", 0));
Для Каждого СтрокаТЧ Из ПустыеСтроки Цикл
    ДокументОбъект.Товары.Удалить(СтрокаТЧ);
КонецЦикла;

Если условие сложнее простого равенства, идите от конца таблицы, зафиксировав число строк до цикла:

ВсегоСтрок = ДокументОбъект.Товары.Количество();
Для Сч = 1 По ВсегоСтрок Цикл
    Индекс = ВсегоСтрок - Сч;
    Если ДокументОбъект.Товары[Индекс].Сумма < 1 Тогда
        ДокументОбъект.Товары.Удалить(Индекс);
    КонецЕсли;
КонецЦикла;

При трёх строках индексы пойдут 2, 1, 0. Удаление строки с большим индексом не сдвигает строки с меньшими, поэтому ничего не теряется.

Массовая запись через таблицу значений и временные таблицы запроса

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

Свернуть дубли номенклатуры можно в три строки:

Таблица = ДокументОбъект.Товары.Выгрузить();
Таблица.Свернуть("Номенклатура, Цена", "Количество, Сумма");
ДокументОбъект.Товары.Загрузить(Таблица);

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

Более сложную обработку удобно делать запросом. Табличную часть передают параметром и сначала помещают во временную таблицу:

Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|    Т.Номенклатура КАК Номенклатура,
|    Т.Цена КАК Цена,
|    Т.Количество КАК Количество,
|    Т.Сумма КАК Сумма
|ПОМЕСТИТЬ ВТТовары
|ИЗ
|    &Товары КАК Т
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
|    ВТТовары.Номенклатура КАК Номенклатура,
|    ВТТовары.Цена КАК Цена,
|    СУММА(ВТТовары.Количество) КАК Количество,
|    СУММА(ВТТовары.Сумма) КАК Сумма
|ИЗ
|    ВТТовары КАК ВТТовары
|ГДЕ
|    ВТТовары.Количество > 0
|
|СГРУППИРОВАТЬ ПО
|    ВТТовары.Номенклатура,
|    ВТТовары.Цена";

Запрос.УстановитьПараметр("Товары",
    ДокументОбъект.Товары.Выгрузить(, "Номенклатура, Цена, Количество, Сумма"));

Результат = Запрос.Выполнить().Выгрузить();
ДокументОбъект.Товары.Загрузить(Результат);
ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);

Здесь запрос убирает строки с нулевым количеством и складывает дубли, а результат сразу ложится в табличную часть. Колонки результата называются так же, как реквизиты строки, поэтому цикл с присваиваниями не нужен. Колонки, которых нет в запросе (ставка НДС, счета учёта), после Загрузить станут пустыми: в рабочем коде добавьте их в оба запроса и в группировку.

Пара правил для таблиц значений в запросах:

  • 🔹 Колонки должны быть типизированы. Таблица из Выгрузить() типы сохраняет, а созданная вручную через Колонки.Добавить("Имя") без описания типов в запрос не пройдёт. Указывайте тип: Колонки.Добавить("Количество", Новый ОписаниеТипов("Число")).
  • 🔹 Если временная таблица нужна в нескольких запросах подряд, создайте Новый МенеджерВременныхТаблиц и присвойте его свойству МенеджерВременныхТаблиц каждого запроса.
  • 🔹 Для выгрузки одной колонки в массив есть ВыгрузитьКолонку("Номенклатура"). Массив удобно передать параметром в условие В (&Список).

Проверка данных перед записью и обработка исключений

Ошибки в строках лучше ловить до записи. Типовой способ — обработчик ОбработкаПроверкиЗаполнения в модуле объекта:

Процедура ОбработкаПроверкиЗаполнения(Отказ, ПроверяемыеРеквизиты)

    Для Каждого СтрокаТЧ Из Товары Цикл
        Если СтрокаТЧ.Количество <= 0 Тогда
            Сообщение = Новый СообщениеПользователю;
            Сообщение.Текст = СтрШаблон("Строка %1: не указано количество",
                Формат(СтрокаТЧ.НомерСтроки, "ЧГ=0"));
            Сообщение.Поле = СтрШаблон("Товары[%1].Количество",
                Формат(СтрокаТЧ.НомерСтроки - 1, "ЧГ=0"));
            Сообщение.УстановитьДанные(ЭтотОбъект);
            Сообщение.Сообщить();
            Отказ = Истина;
        КонецЕсли;
    КонецЦикла;

КонецПроцедуры

В пути к полю индекс строки начинается с нуля, поэтому из НомерСтроки вычитаем единицу. Тогда при открытой форме курсор встанет точно на ошибочную ячейку.

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

При записи из кода надёжнее вызвать проверку явно и не записывать объект, если она не прошла:

Если Не ДокументОбъект.ПроверитьЗаполнение() Тогда
    Возврат;
КонецЕсли;

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

НачатьТранзакцию();
Попытка
    Блокировка = Новый БлокировкаДанных;
    ЭлементБлокировки = Блокировка.Добавить("Документ.РеализацияТоваровУслуг");
    ЭлементБлокировки.УстановитьЗначение("Ссылка", ДокументСсылка);
    Блокировка.Заблокировать();

    ДокументОбъект = ДокументСсылка.ПолучитьОбъект();
    ДокументОбъект.Заблокировать();

    НоваяСтрока = ДокументОбъект.Товары.Добавить();
    НоваяСтрока.Номенклатура = Номенклатура;
    НоваяСтрока.Количество   = Количество;

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

Что делает каждая часть:

  • 🔒 Управляемая блокировка не даёт другому сеансу изменить тот же документ между чтением и записью.
  • 🔒 Заблокировать() у объекта вызывает исключение сразу, если документ открыт и редактируется в форме у другого пользователя.
  • 📝 Запись в журнал регистрации сохраняет полный текст ошибки для разбора.
  • ↩️ ВызватьИсключение без параметров пробрасывает ошибку выше, чтобы вызывающий код не считал операцию успешной.

Встречается совет ставить ДокументОбъект.ОбменДанными.Загрузка = Истина, чтобы запись прошла «без капризов». Этот флаг отключает проверки и обработчики модуля объекта, а проведение в таком режиме не формирует движения. Используйте его только в обработчиках обмена, а не для обхода ошибок.

Запись в табличную часть на управляемой форме

В управляемой форме табличная часть доступна как Объект.Товары. Это уже не табличная часть объекта, а коллекция данных формы (ДанныеФормыКоллекция). Методы похожи, но есть отличия.

ДействиеОбъект (сервер)Форма, Объект.Товары
Добавить строкуДобавить()Добавить(), на клиенте и сервере
Найти по одной колонкеНайти()метода нет, используйте НайтиСтроки()
Строка по идентификатору—НайтиПоИдентификатору()
Выгрузить/Загрузить ТЗдоступнотолько на сервере
Записать в базуЗаписать() объектаЗаписать() формы

Добавление строки по кнопке на клиенте:

&НаКлиенте
Процедура ДобавитьСтроку(Команда)

    НоваяСтрока = Объект.Товары.Добавить();
    НоваяСтрока.Количество = 1;
    Элементы.Товары.ТекущаяСтрока = НоваяСтрока.ПолучитьИдентификатор();
    Модифицированность = Истина;

КонецПроцедуры

Значения строки, на которой стоит курсор, берут через Элементы.Товары.ТекущиеДанные. Если таблица пустая или курсор не выбран, там будет Неопределено, поэтому проверяйте это первой строкой обработчика.

Пересчёт суммы при изменении количества вешают на событие ПриИзменении колонки:

&НаКлиенте
Процедура ТоварыКоличествоПриИзменении(Элемент)

    ТекущиеДанные = Элементы.Товары.ТекущиеДанные;
    Если ТекущиеДанные = Неопределено Тогда
        Возврат;
    КонецЕсли;
    ТекущиеДанные.Сумма = ТекущиеДанные.Количество * ТекущиеДанные.Цена;

КонецПроцедуры

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

&НаСервере
Процедура СвернутьТоварыНаСервере()

    Таблица = Объект.Товары.Выгрузить();
    Таблица.Свернуть("Номенклатура, Цена", "Количество, Сумма");
    Объект.Товары.Загрузить(Таблица);

КонецПроцедуры

Для вызова экспортных методов модуля объекта используйте пару ДокОбъект = РеквизитФормыВЗначение("Объект") и ЗначениеВРеквизитФормы(ДокОбъект, "Объект"). Между ними работаете с обычной табличной частью объекта.

Изменения в форме сами в базу не попадают. Пользователь сохранит их кнопкой записи, а из кода форму записывают методом Записать(). Для проведения передайте параметр: Записать(Новый Структура("РежимЗаписи", РежимЗаписиДокумента.Проведение)).

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

Частые ошибки при записи значений в табличную часть

Диагностика: выберите симптом

Частые вопросы

Можно ли записать значение в табличную часть проведённого документа без перепроведения?
Технически да, режимом РежимЗаписиДокумента.Запись. Но движения регистров останутся по старым строкам, и отчёты разойдутся с документом. Для проведённых документов используйте режим проведения.
Как добавить колонку в табличную часть типовой конфигурации?
Через расширение конфигурации: заимствуйте документ и добавьте реквизит в его табличную часть. Так доработка переживёт обновление типовой конфигурации.
Чем табличная часть отличается от таблицы значений?
Табличная часть хранится в базе вместе с объектом и имеет фиксированный набор колонок из Конфигуратора. Таблица значений живёт только в памяти, колонки в ней можно добавлять кодом.
Как записать данные в табличную часть справочника?
Так же, как у документа: Справочники.Имя.НайтиПоКоду(...) или ссылка → ПолучитьОбъект() → Добавить() → Записать(). Режима проведения у справочника нет, поэтому Записать() вызывается без параметров.
Почему в запросе к своей таблице значений ошибка о типе колонки?
У колонки не задан тип. Создайте её с Новый ОписаниеТипов(...) или получите таблицу через Выгрузить() из табличной части.