При интеграции сторонних систем с 1С:Предприятием через стандартный интерфейс OData разработчики часто сталкиваются с ошибкой «Использованный HTTP метод запрещен в данном контексте» (HTTP 405 Method Not Allowed). Эта ошибка стабильно возникает при попытке отправить запрос метода PATCH, PUT или POST напрямую на URL табличной части объекта (например, к ресурсу вида .../odata/standard.odata/Document_ИмяДокумента_ТабличнаяЧасть).
В этой статье мы подробно разберем причины такого поведения платформы 1С, изучим архитектурные ограничения интерфейса OData и разработаем надежный, пошаговый алгоритм правильного обновления табличных частей объектов 1С с примерами кода.
Для того чтобы понять природу ошибки, обратимся к объектной модели 1С. В системе 1С:Предприятие табличные части документов, справочников или бизнес-процессов не являются самостоятельными сущностями базы данных. Они не могут существовать в отрыве от своего владельца — родительского объекта (документа-ссылки или элемента справочника).
Стандартный интерфейс OData спроектирован в строгом соответствии с этой моделью:
GET с фильтрацией по Ref_Key (ссылке на владельца), чтобы быстро получить строки конкретного документа.Таким образом, попытка выполнить PATCH напрямую к табличной части всегда будет отклонена сервером 1С с ошибкой 405.
Прежде чем перейти к написанию кода, необходимо усвоить важнейшее правило работы с коллекциями в OData 1С: табличная часть при отправке запроса PATCH или PUT на родительский объект всегда перезаписывается целиком.
Если в вашем документе в 1С сейчас находится 20 строк товаров, а вы отправляете PATCH-запрос к документу, передавая в JSON-пакете табличную часть только с 1 измененной строкой, то после выполнения запроса в документе останется ровно 1 строка. Остальные 19 строк будут безвозвратно удалены.
Из этого правила следует единственно верный алгоритм работы, известный как Read-Modify-Write (Чтение - Модификация - Запись):
GET-запрос к родительскому объекту, чтобы получить его текущее состояние и все строки его табличной части.PATCH, обращаясь к самому документу.Рассмотрим подробнее каждый этап реализации этого алгоритма. Представим, что нам необходимо обновить признак отмены или изменить количество в строках табличной части «Товары» документа «Заказ на перемещение».
Для начала нам необходимо прочитать объект из базы данных по его уникальному идентификатору (GUID). Мы должны сформировать правильный запрос к родительскому объекту. Посмотрим на пример кода на языке 1С:
// Сначала подготавливаем HTTP-соединение
Соединение = Новый HTTPСоединение("myserver.ru", 80, "Администратор", "Пароль");
// Формируем GET-запрос для получения всего документа вместе с ТЧ
ИмяДокумента = "Document_ЗаказНаПеремещение";
ИдентификаторОбъекта = "83a6f31f-e809-11e8-8cd3-50465d037106"; // GUID документа
АдресЗапроса = "ИмяБазы/odata/standard.odata/" + ИмяДокумента + "(guid'" + ИдентификаторОбъекта + "')?$format=json";
ЗапросGET = Новый HTTPЗапрос(АдресЗапроса);
ОтветGET = Соединение.ВызватьHTTPМетод("GET", ЗапросGET);
Если ОтветGET.КодСостояния = 200 Тогда
СтрокаJSON = ОтветGET.ПолучитьТелоКакСтроку();
Иначе
ВызватьИсключение "Не удалось получить объект: " + ОтветGET.КодСостояния;
КонецЕсли;
Получив JSON-представление документа, мы преобразуем его в структуру 1С (или в объект вашей интеграционной системы, например, словарь в Python). Мы локально модифицируем строки табличной части, сохраняя при этом все остальные строки без изменений.
Проанализируем структуру полученного JSON. В нем обязательно будет присутствовать массив с именем вашей табличной части (например, "Товары"). Каждая строка в этом массиве содержит реквизиты строки ТЧ.
Важные требования OData к передаче табличных частей при записи:
"ИмяТЧ@odata.type": "Collection(StandardODATA.ТипРодителя_ИмяРодителя_ИмяТЧ_RowType)"._Key и значением GUID ссылки. Например: "Номенклатура_Key": "83a6f31f-e809...".Рассмотрим пример модификации данных на Python (часто используемом для написания внешних скриптов интеграции):
import requests
# Предположим, мы уже авторизовались и получили документ (GET)
url = "http://myserver.ru/ИмяБазы/odata/standard.odata/Document_ЗаказНаПеремещение(guid'83a6f31f-e809-11e8-8cd3-50465d037106')?$format=json"
response = requests.get(url, auth=('Администратор', 'Пароль'))
doc_data = response.json()
# Извлекаем табличную часть "Товары"
товары = doc_data.get('Товары', [])
# Нам нужно отменить строку с определенным кодом строки (например, КодСтроки = 3)
for row in товары:
if row.get('КодСтроки') == 3:
row['Отменено'] = True
# Подготавливаем JSON-структуру для отправки PATCH-запроса
# Мы передаем ТОЛЬКО то, что хотим обновить, но табличную часть передаем ЦЕЛИКОМ!
payload = {
"Товары@odata.type": "Collection(StandardODATA.Document_ЗаказНаПеремещение_Товары_RowType)",
"Товары": товары
}
Теперь мы отправляем подготовленные данные обратно на сервер. Обратите внимание: запрос отправляется на URL родительского документа, а не его табличной части!
Ниже представлен пример отправки PATCH-запроса из встроенного языка 1С:
// Предположим, в переменной "НоваяТЧ" у нас находится массив измененных строк
// и мы сформировали JSON-тело запроса в переменной "ТелоЗапросаJSON"
ЗапросPATCH = Новый HTTPЗапрос(АдресЗапроса); // URL родительского документа
ЗапросPATCH.УстановитьТелоИзСтроки(ТелоЗапросаJSON, КодировкаТекста.UTF8);
// Устанавливаем необходимый заголовок для формата JSON
ЗапросPATCH.Заголовки.Вставить("Content-Type", "application/json");
ОтветPATCH = Соединение.ВызватьHTTPМетод("PATCH", ЗапросPATCH);
Если ОтветPATCH.КодСостояния = 200 Тогда
ЗаписатьЛогСобытий("Табличная часть успешно обновлена!");
Иначе
ВызватьИсключение "Ошибка при обновлении ТЧ через OData: " + ОтветPATCH.ПолучитьТелоКакСтроку();
КонецЕсли;
Поскольку процесс обновления табличной части разбит на два шага (сначала GET, затем PATCH), существует вероятность возникновения конкурентного доступа. В период времени между тем, как ваше внешнее приложение прочитало документ, и тем, как оно отправило команду на изменение, другой пользователь в интерфейсе 1С мог изменить этот же документ.
Для предотвращения затирания чужих изменений в OData 1С встроен механизм оптимистической блокировки данных при помощи HTTP-заголовков ETag и If-Match:
GET-запроса сервер 1С возвращает в заголовке ответа параметр ETag (или свойство DataVersion в теле JSON). Это уникальный хэш текущей версии объекта в базе данных.PATCH-запроса вы обязаны передать полученный ETag в заголовке запроса If-Match: "ваша_версия_данных".GET), наложить свои изменения поверх новых данных и повторить попытку записи.Если вы уверены, что хотите принудительно перезаписать данные независимо от изменений в системе, вы можете передать в заголовке значение звездочки: If-Match: *. Однако использовать этот подход рекомендуется с осторожностью.
Давайте обобщим ключевые правила успешного изменения табличных частей 1С через OData REST API:
PATCH / POST / PUT запросы непосредственно к ресурсам табличных частей.@odata.type.If-Match для обеспечения целостности данных в многопользовательской среде.Следуя этим правилам, вы сможете построить стабильную, масштабируемую и бесконфликтную интеграцию ваших внешних информационных систем с решением на платформе 1С:Предприятие.