При разработке интеграций веб-сайтов, мобильных приложений или внешних сервисов с 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-запрос по идентификатору документа Document_ПоступлениеТоваровУслуг(guid'...'). При попытке передать массив строк табличной части Услуги без дополнительных служебных полей сервер может вернуть 500 Internal Server Error ("Произошла внутренняя ошибка OData сервиса").
Разберем по шагам, из-за чего возникает эта ошибка и как ее исправить:
LineNumber (номер строки: 1, 2, 3 и т.д.). Без этого поля OData-сервер не может корректно проинициализировать коллекцию строк и падает с внутренней ошибкой.Document_ПоступлениеТоваровУслуг необходимо явно передавать реквизит ВидОперации = "Услуги" (или соответствующее значение перечисления), иначе логика конфигурации проигнорирует или сотрет строки услуг.Наиболее эффективным и правильным способом является создание документа и всех его табличных частей в рамках одного 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"
}
]
}
Если документ уже был создан ранее, и нам требуется перезаписать его табличную часть, мы используем 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.
Рассмотрим пример реализации отправки данных в 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 учитывайте следующие нюансы:
ПриИзменении цены или количества) не вызываются. Если в модуле объекта документа не прописан принудительный пересчет зависимых полей в процедуре ПередЗаписью, все производные реквизиты (Сумма, СуммаНДС, Всего) необходимо рассчитывать на стороне клиента интеграции и явно передавать в JSON.GET /odata/standard.odata/$metadata.POST /odata/standard.odata/Document_Имя(guid'GUID')/Post?PostingModeOperational=false.