Как заполнить табличную часть документа 1С через OData?

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

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

Анализ проблемы: почему нельзя создать строку ТЧ отдельным запросом

Рассмотрим типичный сценарий: разработчик пытается отправить POST-запрос напрямую к ресурсу табличной части документа, например к Document_ПоступлениеТоваровУслуг_Услуги, передавая ссылку на документ Ref_Key и данные строки:


POST /odata/standard.odata/Document_ПоступлениеТоваровУслуг_Услуги()?$format=application/json
{
    "Ref_Key": "ea192958-f1b7-11e6-8da2-50e549a0019a",
    "Количество": 4000,
    "Цена": 3.4,
    "СтавкаНДС": "НДС18"
}

В ответ 1С возвращает ошибку с кодом 13: "Создание строк табличной части напрямую не поддерживается". Проанализируем причину такого поведения: в объектной модели 1С табличные части не являются самостоятельными сущностями базы данных, обладающими собственным жизненным циклом. Они существуют исключительно в контексте своего объекта-владельца (документа или справочника). Соответственно, протокол OData в платформе 1С:Предприятие запрещает изолированное добавление одной строки через POST к сущности табличной части.

Ошибки при обновлении документа через PATCH и способ их решения

Посмотрим на следующий шаг: передачу всей табличной части целиком через PATCH-запрос по идентификатору документа Document_ПоступлениеТоваровУслуг(guid'...'). При попытке передать массив строк табличной части Услуги без дополнительных служебных полей сервер может вернуть 500 Internal Server Error ("Произошла внутренняя ошибка OData сервиса").

Разберем по шагам, из-за чего возникает эта ошибка и как ее исправить:

  1. Обязательное наличие поля номера строки: в каждом объекте строки табличной части в JSON-массиве обязательно должно присутствовать поле LineNumber (номер строки: 1, 2, 3 и т.д.). Без этого поля OData-сервер не может корректно проинициализировать коллекцию строк и падает с внутренней ошибкой.
  2. Заполнение ключевых реквизитов шапки: в конфигурациях 1С (например, 1С:Бухгалтерия) отображение и проведение табличных частей зависит от реквизитов шапки документа. Например, для документа Document_ПоступлениеТоваровУслуг необходимо явно передавать реквизит ВидОперации = "Услуги" (или соответствующее значение перечисления), иначе логика конфигурации проигнорирует или сотрет строки услуг.

Вариант 1: Создание документа с табличной частью одним POST-запросом

Наиболее эффективным и правильным способом является создание документа и всех его табличных частей в рамках одного POST-запроса к сущности документа. В этом случае документ создается сразу со всеми строками без необходимости отправлять дополнительные запросы.

Рассмотрим пример минимального JSON-пакета для создания акта поступления услуг:


POST /odata/standard.odata/Document_ПоступлениеТоваровУслуг?$format=application/json
Content-Type: application/json

{
    "Организация_Key": "69f89d71-e7c1-11e6-a39c-50e549a0019a",
    "Контрагент_Key": "cc2f1829-e7c3-11e6-a39c-50e549a0019a",
    "ВидОперации": "Услуги",
    "Услуги": [
        {
            "LineNumber": 1,
            "Количество": 4000,
            "Цена": 3.4,
            "СтавкаНДС": "НДС18"
        },
        {
            "LineNumber": 2,
            "Количество": 100,
            "Цена": 150.0,
            "СтавкаНДС": "НДС18"
        }
    ]
}

Вариант 2: Обновление табличной части существующего документа (PATCH)

Если документ уже был создан ранее, и нам требуется перезаписать его табличную часть, мы используем HTTP-метод PATCH. Важно помнить: при передаче табличной части методом PATCH или PUT переданный массив полностью перезаписывает существующие строки документа, а не дописывает новые к старым.

Посмотрим на структуру запроса изменения документа по GUID:


PATCH /odata/standard.odata/Document_ПоступлениеТоваровУслуг(guid'01d22eff-f1d2-11e6-8da2-50e549a0019a')?$format=application/json
Content-Type: application/json

{
    "ВидОперации": "Услуги",
    "Услуги": [
        {
            "LineNumber": 1,
            "Количество": 4000,
            "Цена": 3.4,
            "СтавкаНДС": "НДС18"
        }
    ]
}

Если вам требуется добавить строку к уже существующим в документе, необходимо сначала выполнить GET-запрос, получить текущий массив строк, добавить к нему новый элемент с инкрементированным LineNumber и отправить результирующий массив обратно через PATCH.

Пример реализации клиента на PHP

Рассмотрим пример реализации отправки данных в 1С OData на языке PHP с использованием популярного HTTP-клиента Guzzle:


use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\RequestException as RequestExceptionGuzzle;

class OData1CClient
{
    private string $server;

    public function __construct(string $serverUrl)
    {
        $this->server = rtrim($serverUrl, '/');
    }

    /**
     * Отправка данных в OData 1C
     *
     * @param string $method1C Метод OData, например "Document_ПоступлениеТоваровУслуг"
     * @param array $data Массив данных для отправки
     * @param string $method HTTP-метод: POST, PATCH, GET, DELETE
     * @param string|null $guid GUID объекта (если применимо)
     * @param array $params Дополнительные параметры URL
     * @return array
     */
    public function sendData(string $method1C, array $data, string $method = 'GET', ?string $guid = null, array $params = []): array
    {
        $client = new Client([
            'auth' => ['login', 'password']
        ]);

        $resource = $method1C;
        if (!empty($guid)) {
            $resource .= "(guid'$guid')";
        }

        $url = $this->getUrl($resource, $params);

        try {
            $options = [];
            if (!empty($data)) {
                $options['json'] = $data;
            }

            $res = $client->request($method, $url, $options);
            $content = $res->getBody()->getContents();
            return json_decode($content, true) ?? [];
        } catch (ClientException | RequestExceptionGuzzle $ex) {
            $responseBody = $ex->hasResponse() ? $ex->getResponse()->getBody()->getContents() : $ex->getMessage();
            throw new \Exception("Ошибка OData: " . $responseBody, $ex->getCode(), $ex);
        }
    }

    private function getUrl(string $resource, array $params = []): string
    {
        $url = $this->server . '/' . $resource . '?$format=application/json';
        foreach ($params as $name => $value) {
            $url .= '&' . urlencode($name) . '=' . urlencode($value);
        }
        return $url;
    }
}

Важные особенности работы с OData в 1С

При проектировании обмена данными через OData учитывайте следующие нюансы:

  1. Автоматический пересчет сумм: OData работает с объектами базы данных в обход экранных форм. Обработчики событий форм (например, ПриИзменении цены или количества) не вызываются. Если в модуле объекта документа не прописан принудительный пересчет зависимых полей в процедуре ПередЗаписью, все производные реквизиты (Сумма, СуммаНДС, Всего) необходимо рассчитывать на стороне клиента интеграции и явно передавать в JSON.
  2. Проверка схемы через $metadata: для выяснения точных системных имен табличных частей, обязательности полей и допустимых типов значений выполните запрос к конечной точке метаданных: GET /odata/standard.odata/$metadata.
  3. Проведение документов: по умолчанию OData-запросы сохраняют документы в непроведенном виде. Для проведения созданного документа вызовите операцию проведения: POST /odata/standard.odata/Document_Имя(guid'GUID')/Post?PostingModeOperational=false.
← На главную