Показать сообщение отдельно
Старый 23.06.2020, 14:12   #1  
Blog bot is offline
Blog bot
Участник
 
25,459 / 846 (79) +++++++
Регистрация: 28.10.2006
littleax: Simple Rest API in D365FO, D365F, D365SCM
Источник: http://littleax.blogspot.com/2020/06...f-d365scm.html
==============

Dynamics 365 FO Rest API




Вступление



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


Практически все материалы показаны в SMART TALKS 203 https://www.youtube.com/watch?v=yuJx6J4edio
Что такое XML




XML это текстовы формат представления данных. У него есть заголовок, и произвольный набор тегов. Теги должны быть парными. Внутри тегов может быть представлены разлиичные типы данных как простые (тескст, число, дата), так и более сложных - например список элементов.


XML выглядит приблизительно так



<span style="color: #f92672;"SalesOrder/span>
<span style="color: #f92672;"OrderId/span>"SO-00023"</span style="color: #f92672;"OrderId/span>
<span style="color: #f92672;"CustomerId/span>"Very impotant customer"</span style="color: #f92672;"CustomerId/span>
<span style="color: #f92672;"OrderLines/span>
<span style="color: #f92672;"SalesOrderLine/span>
<span style="color: #f92672;"OrderId/span>"SO-00023"</span style="color: #f92672;"OrderId/span>
<span style="color: #f92672;"ItemId/span>"It00123-5"</span style="color: #f92672;"ItemId/span>
<span style="color: #f92672;"Qty/span>5</span style="color: #f92672;"Qty/span>
<span style="color: #f92672;"Price/span>10</span style="color: #f92672;"Price/span>
<span style="color: #f92672;"Amount/span>50</span style="color: #f92672;"Amount/span>
</span style="color: #f92672;"SalesOrderLine/span>
<span style="color: #f92672;"SalesOrderLine/span>
<span style="color: #f92672;"OrderId/span>"SO-00023"</span style="color: #f92672;"OrderId/span>
<span style="color: #f92672;"ItemId/span>"It00123-6"</span style="color: #f92672;"ItemId/span>
<span style="color: #f92672;"Qty/span>1</span style="color: #f92672;"Qty/span>
<span style="color: #f92672;"Price/span>15</span style="color: #f92672;"Price/span>
<span style="color: #f92672;"Amount/span>15</span style="color: #f92672;"Amount/span>
</span style="color: #f92672;"SalesOrderLine/span>
</span style="color: #f92672;"OrderLines/span>
</span style="color: #f92672;"SalesOrder/span>




Что такое JSON




Так же как и XML - текстовый формат для передачи данных, очень похож на XML только натация чуть чуть другая. Рассмотрим как выглядят эти же данные в формате JSON


{
"SalesOrder": {
"OrderId": "SO-00023",
"CustomerId": "Very impotant customer",
"OrderLines": {
"SalesOrderLine": [
{
"OrderId": "SO-00023",
"ItemId": "It00123-5",
"Qty": 5,
"Price": 10,
"Amount": 50
},
{
"OrderId": "SO-00023",
"ItemId": "It00123-6",
"Qty": 1,
"Price": 15,
"Amount": 15
}
]
}
}
}




В чем разница или почему используют один или другой формат? Исторически сложилось, так что сначала был XML и он позволял описывать необходимые данные. К недостаткам можно отнести большую громоздкость (на больших объемах данных это становится важным). JSON (JavaScript object notation) - был взят из JavaScript где он имеет нативную поддержку и получил распространение для обмена данными как более легковестная альтернатива XML. С точки зрения Enterprise - есть протокол SOAP - с использованием XML, есть протокол Rest API - с использованием XML. SOAP дает возможность получить схему документа, REST API - нет. SOAP - тяжелый, REST легковестный. В D365FO* можно применять любой из подходов либо их комбинацию. Мы заглянем в схему XML но в основном будем рассматривать JSON.


Получение ключей


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

Авторизация дело не хитрое и у него есть свои особенности. Предположим наше приложение живет по какому-то адресу ... например https://devboxa5adevaos.cloudax.dynamics.com/?cmp=USMF&mi=DefaultDashboard - это точка входа в приложение. Значит наш базовый URL = devboxa5adevaos.cloudax.dynamics.com


Для того что бы мы получили доступ к приложению как пользователи - необходимо что бы администратор нас добавил как пользователь и назначил некоторые права в системе.


С точки зрения интеграции все в принципе работает так же. Мы специальным образом подключаем интеграцию к пользователю системы. Пользователю системы даем права на объекты системы. Подробная инчтрукция тут, а короткое описание ниже ...
Мы должны в Azure portal создать новую регастрацию для нашего приложения. При этом мы получим пару значений : clientId, client secret. Эти значения нужно запомнить!
Переходим в D365FO, System administration\setup\Azure Active Directory application и создаем новую запись





1 - вносим client Id
2 - описание записи (обычно назначение канала или название системы)
3 - пользователь Акс


Таким образом мы связали clientId с пользователем Акс. Осталось передать clientId, client secret внешней сестеме.



Отступление. Что такое запрос, какие они бывают, что такое Postman





Давайте попробуем разобаться что такое запрос. Опять же, все упрощаем, главное сейчас - уловить суть.
Если мы откроем любую страницу в браузере, это и будет обычный запрос GET. Можно даже посмотреть что это GET и что он отправляет, получает.

Google chrome, правой клавишей на странице в любом месте в сплывающем контекстном меню выбрать Inspect (Ctrl+Shift+I), и переходим на закладку Network



Мы увидим, вот такую картину







Номер 5- показывает запрос GET, куда он был сделан, что он получил ответ сервера (Status code) 200. Сам ответ - на закладке Response


Какие еще могут быть запросы - может быть (взято отсюда)



HTTP MethodCRUDEntire Collection (e.g. /users)Specific Item (e.g. /users/123)POSTCreate201 (Created), ‘Location’ header with link to /users/{id} containing new ID.Avoid using POST on single resourceGETRead200 (OK), list of users. Use pagination, sorting and filtering to navigate big lists.200 (OK), single user. 404 (Not Found), if ID not found or invalid.PUTUpdate/Replace405 (Method not allowed), unless you want to update every resource in the entire collection of resource.200 (OK) or 204 (No Content). Use 404 (Not Found), if ID not found or invalid.PATCHPartial Update/Modify405 (Method not allowed), unless you want to modify the collection itself.200 (OK) or 204 (No Content). Use 404 (Not Found), if ID not found or invalid.DELETEDelete405 (Method not allowed), unless you want to delete the whole collection — use with caution.200 (OK). 404 (Not Found), if ID not found or invalid.



Что бы увидеть как отдает данные D365FO давайте попробуем выполнить такой запрос в адресной строке браузера


https://devboxa5adevaos.cloudax.dynamics.com/data/CustomerGroups(dataAreaId='usmf',CustomerGroupId='10')


И посмотрим на свойства в окошке Inspect



если данных на закладке Network нет - нажмите F5, если страница не открывается вовсе - выполните авторизацию в D365FO



Мы выполнили Rest API запрос GET к CustomersGroups отобрав данные только по полям DataAreaId = 'usmf' и CustomerGroupId = '10'


К сожалению возможности в браузере ограничены и нам будет проще в дальнейшем использовать другие инструменты, например CURL, Fiddler, Postman.



Postman




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



Где его взять ... - тут






1. Коллекции - тут создаем для себя папочку с текущим нашим проектом. Здесь будут размещаться наши сохраненные запросы (создали, описали)
2. Запросы - создаются плюсиком - основна рабочая область
3. Окружение - сюда можно будет заносить значения переменых. Об этом позже, но в двух словах, мы можем базовый URL записать в переменную и один и тот же запрос выполнять для разных окружений - в каждом окружении значение этой переменной базовый URL может быть свое ...



Что бы создать запрос - нажимаем + из 2)






1 - можно запросу дать имя
2 - тип запроса GET, POST ...
3 - к чему запрос
4 - заголовки - рассмотрим дальше отдельно. В двух словах - мы говорим сервиису что мы от него хотим - например мы ему можем сообщить что хотим получить результат в формате xml или json. Другой пример - здесь задается токен авторизации
5 - тело запроса. Если мы хотим получить данные от сервиса - тело пустое. А если хотим вставить данные - то в этой секции описываем что хотим вставить
6 - отправить запрос на выполнение


Пример запроса который мы использовали в браузере







Результат - внизу - на закладке Body





Браузер таблиц


Мы можем просмотреть записи таблицы если введем сслыку в виде


https://BASEURL/?mi=SysTableBrowser&TableName=CustGroup&cmp=USMF&lng=en-us&limitednav=true


где
TableName=Имя Таблицы или представления (View) - CustGroup

cmp - имя компании - USMF
lng - язык - un-us


В результате получим










Список Entity

Чтобы получить список энтитей - необходимо ввести


https://BASEURL/data/





Table and Entity Chrome extension







Просто необходимый инструмент. Вводится BASEURL, есть возможность получить список всех таблиц, View, Entity с возможностью найти по названию, просмотреть свойства ...



Однако, вернемся к запросам .....




Получение токена авторизации


Авториация производится при помощи протокола OAuth 2.0


Для этого выполняется запрос



POST https://login.windows.net//oauth2/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: login.windows.net

resource=https%3A%2F%2FBASEURL&client_id=f78fe522-29b3-4acf-a61b-f5581e121496&client_secret=bZGMw90F7SwVyBCuIllJH7umNu2SutM0qOczMPUOzOg%3D&grant_type=client_credentials

где
- это значение нужно уточнить у технических специалистов либо в D365 знак вопроса, About/в секци This product is licensed to:


BASEURL- значение описыно выше
client_id, client_secret - значения которые мы запомнили выше


Запустим этот запрос в Postman - в результате мы получим ответ типа







В крассном квадрате access_token - который будет использован для авторизации. Этот токен который будет использоваться в каждом запросе.



Обратите внимание на использование переменных в секции body. Эти переменные установлены в окружении





Если в запросе на авторизацию на закладке Tests этот код
pm.environment.set("token", pm.response.json().access_token);







и выполнить запрос еще раз - то в переменную token будет записано вот это значение и мы сможем дальше ее использовать в запросах.



Запрос на чтение данных


Чтение данных происходит запросом GET


Запрос к Entity
CustomerGroups
вернет 9 записей





$top


$top - параметер - вернет первых нескоко записей


Например в этом же запросе GET
https://[COLOR=var(--brand-primary)]{{base_url}}[/COLOR]/data/CustomerGroups?$top=1


Вернет только одну запись (первую запись)



{
"@odata.context": "https://devaos.cloudax.dynamics.com/data/$metadata#CustomerGroups",
"value": [
{
"@odata.etag": "W/\"JzAsMjI1NjU0MjExNzYn\"",
"dataAreaId": "usmf",
"CustomerGroupId": "10",
"ClearingPeriodPaymentTermName": "Net30",
"CustomerAccountNumberSequence": "",
"DefaultDimensionDisplayValue": "",
"Description": "Wholesales customers",
"IsSalesTaxIncludedInPrice": "No",
"WriteOffReason": "",
"PaymentTermId": "Net30",
"TaxGroupId": ""
}
]
}




$skip


$skip - параметр - пропустит первых несколько записей


В этом запросе
GET https://[COLOR=var(--brand-primary)]{{base_url}}[/COLOR]/data/CustomerGroups?$top=1&$skip=1



Вернет только вторую запись



{
"@odata.context": "https://devaos.cloudax.dynamics.com/data/$metadata#CustomerGroups",
"value": [
{
"@odata.etag": "W/\"Jzc1MDAwNDY4LDIyNTY1NDIxMTc3Jw==\"",
"dataAreaId": "usmf",
"CustomerGroupId": "20",
"ClearingPeriodPaymentTermName": "Net30",
"CustomerAccountNumberSequence": "",
"DefaultDimensionDisplayValue": "",
"Description": "Major customers new",
"IsSalesTaxIncludedInPrice": "No",
"WriteOffReason": "GOODWILL",
"PaymentTermId": "Net30",
"TaxGroupId": ""
}
]
}




$count


Возвращает кол-во записей запроса


В этом виде GET
https://[COLOR=var(--brand-primary)]{{base_url}}[/COLOR]/data/CustomerGroups/$count


Получаем 9






Запрос
GET https://[COLOR=var(--brand-primary)]{{base_url}}[/COLOR]/data/CustomerGroups?$count=true



Вернет



{
"@odata.context": "https://devaos.cloudax.dynamics.com/data/$metadata#CustomerGroups",
"@odata.count": 9,
"value": [
{
"@odata.etag": "W/\"JzAsMjI1NjU0MjExNzYn\"",
"dataAreaId": "usmf",
"CustomerGroupId": "10",
"ClearingPeriodPaymentTermName": "Net30",
"CustomerAccountNumberSequence": "",
"DefaultDimensionDisplayValue": "",
"Description": "Wholesales customers",
"IsSalesTaxIncludedInPrice": "No",
"WriteOffReason": "",
"PaymentTermId": "Net30",
"TaxGroupId": ""
},
{




и т.д. ...


Запрос GET
https://[COLOR=var(--brand-primary)]{{base_url}}[/COLOR]/data/CustomerGroups?$count=true&$top=1


Вернет все равно 9 записей


{
"@odata.context": "https://devaos.cloudax.dynamics.com/data/$metadata#CustomerGroups",
"@odata.count": 9,
"value": [
{
"@odata.etag": "W/\"JzAsMjI1NjU0MjExNzYn\"",
"dataAreaId": "usmf",
"CustomerGroupId": "10",
"ClearingPeriodPaymentTermName": "Net30",
"CustomerAccountNumberSequence": "",
"DefaultDimensionDisplayValue": "",
"Description": "Wholesales customers",
"IsSalesTaxIncludedInPrice": "No",
"WriteOffReason": "",
"PaymentTermId": "Net30",
"TaxGroupId": ""
}
]
}




$select



Указываем список полей для выборки





GET https://[COLOR=var(--brand-primary)]{{base_url}}[/COLOR]/data/CustomerGroups?$top=2&$select=dataAreaId,CustomerGroupId


Получим


{
"@odata.context": "https://devaos.cloudax.dynamics.com/data/$metadata#CustomerGroups(dataAreaId,CustomerGroupId)",
"value": [
{
"@odata.etag": "W/\"JzAsMjI1NjU0MjExNzYn\"",
"dataAreaId": "usmf",
"CustomerGroupId": "10"
},
{
"@odata.etag": "W/\"Jzc1MDAwNDY4LDIyNTY1NDIxMTc3Jw==\"",
"dataAreaId": "usmf",
"CustomerGroupId": "20"
}
]
}




$filter




фильтруем записи


GET https://[COLOR=var(--brand-primary)]{{base_url}}[/COLOR]/data/CustomerGroups?$count=true&$filter=PaymentTermId eq 'Net30'


{
"@odata.context": "https://devaos.cloudax.dynamics.com/data/$metadata#CustomerGroups",
"@odata.count": 2,
"value": [
{
"@odata.etag": "W/\"JzAsMjI1NjU0MjExNzYn\"",
"dataAreaId": "usmf",
"CustomerGroupId": "10",
"ClearingPeriodPaymentTermName": "Net30",
"CustomerAccountNumberSequence": "",
"DefaultDimensionDisplayValue": "",
"Description": "Wholesales customers",
"IsSalesTaxIncludedInPrice": "No",
"WriteOffReason": "",
"PaymentTermId": "Net30",
"TaxGroupId": ""
},
{
"@odata.etag": "W/\"Jzc1MDAwNDY4LDIyNTY1NDIxMTc3Jw==\"",
"dataAreaId": "usmf",
"CustomerGroupId": "20",
"ClearingPeriodPaymentTermName": "Net30",
"CustomerAccountNumberSequence": "",
"DefaultDimensionDisplayValue": "",
"Description": "Major customers new",
"IsSalesTaxIncludedInPrice": "No",
"WriteOffReason": "GOODWILL",
"PaymentTermId": "Net30",
"TaxGroupId": ""
}
]
}


Допускается использовать AND, OR и





$orderby



сортируем записи, по умолчанию ASK, доступно использование DESC



GET https://[COLOR=var(--brand-primary)]{{base_url}}[/COLOR]/data/CustomerGroups?$count=true&$filter=PaymentTermId eq 'Net30'&$orderby=Description


{
"@odata.context": "https://devaos.cloudax.dynamics.com/data/$metadata#CustomerGroups",
"@odata.count": 2,
"value": [
{
"@odata.etag": "W/\"Jzc1MDAwNDY4LDIyNTY1NDIxMTc3Jw==\"",
"dataAreaId": "usmf",
"CustomerGroupId": "20",
"ClearingPeriodPaymentTermName": "Net30",
"CustomerAccountNumberSequence": "",
"DefaultDimensionDisplayValue": "",
"Description": "Major customers new",
"IsSalesTaxIncludedInPrice": "No",
"WriteOffReason": "GOODWILL",
"PaymentTermId": "Net30",
"TaxGroupId": ""
},
{
"@odata.etag": "W/\"JzAsMjI1NjU0MjExNzYn\"",
"dataAreaId": "usmf",
"CustomerGroupId": "10",
"ClearingPeriodPaymentTermName": "Net30",
"CustomerAccountNumberSequence": "",
"DefaultDimensionDisplayValue": "",
"Description": "Wholesales customers",
"IsSalesTaxIncludedInPrice": "No",
"WriteOffReason": "",
"PaymentTermId": "Net30",
"TaxGroupId": ""
}
]
}



KEY

Возможность выбирать данные по ключу - одну запись. Очень важное свойство в операциях PATCH, PUT, DELETE



GET https://[COLOR=var(--brand-primary)]{{base_url}}[/COLOR]/data/CustomerGroups(dataAreaId='usmf',CustomerGroupId='10')


Ответ


{
"@odata.context": "https://devaos.cloudax.dynamics.com/data/$metadata#CustomerGroups/$entity",
"@odata.etag": "W/\"JzAsMjI1NjU0MjExNzYn\"",
"dataAreaId": "usmf",
"CustomerGroupId": "10",
"ClearingPeriodPaymentTermName": "Net30",
"CustomerAccountNumberSequence": "",
"DefaultDimensionDisplayValue": "",
"Description": "Wholesales customers",
"IsSalesTaxIncludedInPrice": "No",
"WriteOffReason": "",
"PaymentTermId": "Net30",
"TaxGroupId": ""
}




как узнать ключ


Accept: odata.metadata=minimal, odata.metadata=full

Ключ Accept говорит о том, как мы ожидаем получить данные от сервиса
В случае odata.metadata=minimal - сервис выдаст минимальный набор данных - все запросы до этого момента были выполенены с этим параметром
Как выглядит ответ odata.metadata=full вернет такой ответ


GET https://[COLOR=var(--brand-primary)]{{base_url}}[/COLOR]/data/CustomerGroups?$count=true&$filter=CustomerGroupId eq '10'


{
"@odata.context": "https://devaos.cloudax.dynamics.com/data/$metadata#CustomerGroups",
"@odata.count": 1,
"value": [
{
"@odata.type": "#Microsoft.Dynamics.DataEntities.CustomerGroup",
"@odata.id": "https://devaos.cloudax.dynamics.com/data/CustomerGroups(dataAreaId='usmf',CustomerGroupId='10')",
"@odata.etag": "W/\"JzAsMjI1NjU0MjExNzYn\"",
"@odata.editLink": "CustomerGroups(dataAreaId='usmf',CustomerGroupId='10')",
"dataAreaId": "usmf",
"CustomerGroupId": "10",
"ClearingPeriodPaymentTermName": "Net30",
"CustomerAccountNumberSequence": "",
"DefaultDimensionDisplayValue": "",
"Description": "Wholesales customers",
"IsSalesTaxIncludedInPrice@odata.type": "#Microsoft.Dynamics.DataEntities.NoYes",
"IsSalesTaxIncludedInPrice": "No",
"WriteOffReason": "",
"PaymentTermId": "Net30",
"TaxGroupId": "",
"DimensionSet@odata.associationLink": "https://devaos.cloudax.dynamics.com/data/CustomerGroups(dataAreaId='usmf',CustomerGroupId='10')/DimensionSet/$ref",
"DimensionSet@odata.navigationLink": "https://devaos.cloudax.dynamics.com/data/CustomerGroups(dataAreaId='usmf',CustomerGroupId='10')/DimensionSet",
"Prospects@odata.associationLink": "https://devaos.cloudax.dynamics.com/data/CustomerGroups(dataAreaId='usmf',CustomerGroupId='10')/Prospects/$ref",
"Prospects@odata.navigationLink": "https://devaos.cloudax.dynamics.com/data/CustomerGroups(dataAreaId='usmf',CustomerGroupId='10')/Prospects"
}
]
}


где
@odata.type - название Entity
@odata.id - ссылка на получение уникальной текущей записи - в скобках - ключи
@odata.nextLink - ссылка на следующую страницу






Ограничения


Максимальная выборка ограничена только 1000 записями. Если в результирующей выборке записей больше - включается постраничное оображение. Постраничное отображение реализовано при помощи $top и $skip. Дополнительно появляется тег @odata.nextLink со ссылкой на следующий набор записей.



$expand




Позволяет присоединить один набор данных в другому


Возмем другой набор данных - GET
https://[COLOR=var(--brand-primary)]{{base_url}}[/COLOR]/data/SalesOrderHeadersV2?$top=1




{
"@odata.context": "https://devaos.cloudax.dynamics.com/data/$metadata#SalesOrderHeadersV2/$entity",
"@odata.type": "#Microsoft.Dynamics.DataEntities.SalesOrderHeaderV2",
"@odata.id": "https://adevaos.cloudax.dynamics.com/data/SalesOrderHeadersV2(dataAreaId='usmf',SalesOrderNumber='000002')",
"@odata.etag": "W/\"JzAsNTYzNzE0NDU3NjswLDA7MCwwOzAsMDswLDA7MCwwJw==\"",
"@odata.editLink": "SalesOrderHeadersV2(dataAreaId='usmf',SalesOrderNumber='000002')",
"dataAreaId": "usmf",
"SalesOrderNumber": "000002",
"OrderTotalChargesAmount@odata.type": "#Decimal",
......

"RevRecContractStartDate": "1900-01-01T12:00:00Z",
"SalesOrderOrigin@odata.associationLink": "https://devaos.cloudax.dynamics.com/data/SalesOrderHeadersV2(dataAreaId='usmf',SalesOrderNumber='000002')/SalesOrderOrigin/$ref",
"SalesOrderOrigin@odata.navigationLink": "https://devaos.cloudax.dynamics.com/data/SalesOrderHeadersV2(dataAreaId='usmf',SalesOrderNumber='000002')/SalesOrderOrigin",
"Project@odata.associationLink": "https://devaos.cloudax.dynamics.com/data/SalesOrderHeadersV2(dataAreaId='usmf',SalesOrderNumber='000002')/Project/$ref",
"Project@odata.navigationLink": "https://devaos.cloudax.dynamics.com/data/SalesOrderHeadersV2(dataAreaId='usmf',SalesOrderNumber='000002')/Project",
"DimensionSet@odata.associationLink": "https://devaos.cloudax.dynamics.com/data/SalesOrderHeadersV2(dataAreaId='usmf',SalesOrderNumber='000002')/DimensionSet/$ref",
"DimensionSet@odata.navigationLink": "https://devaos.cloudax.dynamics.com/data/SalesOrderHeadersV2(dataAreaId='usmf',SalesOrderNumber='000002')/DimensionSet",
"SalesOrderLines@odata.associationLink": "https://devaos.cloudax.dynamics.com/data/SalesOrderHeadersV2(dataAreaId='usmf',SalesOrderNumber='000002')/SalesOrderLines/$ref",
"SalesOrderLines@odata.navigationLink": "https://devaos.cloudax.dynamics.com/data/SalesOrderHeadersV2(dataAreaId='usmf',SalesOrderNumber='000002')/SalesOrderLines",
"QualityOrderHeaders@odata.associationLink": "https://devaos.cloudax.dynamics.com/data/SalesOrderHeadersV2(dataAreaId='usmf',SalesOrderNumber='000002')/QualityOrderHeaders/$ref",
"QualityOrderHeaders@odata.navigationLink": "https://devaos.cloudax.dynamics.com/data/SalesOrderHeadersV2(dataAreaId='usmf',SalesOrderNumber='000002')/QualityOrderHeaders"
}






Теперь его расширим за счет expand


Запрос GET https://[COLOR=var(--brand-primary)]{{base_url}}[/COLOR]/data/SalesOrderHeadersV2(dataAreaId='usmf',SalesOrderNumber='000002')?$expand=SalesOrderLines


Ответ


{
"@odata.context": "https://devaos.cloudax.dynamics.com/data/$metadata#SalesOrderHeadersV2/$entity",
"@odata.type": "#Microsoft.Dynamics.DataEntities.SalesOrderHeaderV2",
"@odata.id": "https://devaos.cloudax.dynamics.com/data/SalesOrderHeadersV2(dataAreaId='usmf',SalesOrderNumber='000002')",
"@odata.etag": "W/\"JzAsNTYzNzE0NDU3NjswLDA7MCwwOzAsMDswLDA7MCwwJw==\"",
"@odata.editLink": "SalesOrderHeadersV2(dataAreaId='usmf',SalesOrderNumber='000002')",
"dataAreaId": "usmf",
"SalesOrderNumber": "000002",
"OrderTotalChargesAmount@odata.type": "#Decimal",
"OrderTotalChargesAmount": 0,
...
"RevRecLatestReverseJournal": 0,
"RevRecContractStartDate@odata.type": "#DateTimeOffset",
"RevRecContractStartDate": "1900-01-01T12:00:00Z",
"SalesOrderOrigin@odata.associationLink": "https://devaos.cloudax.dynamics.com/data/SalesOrderHeadersV2(dataAreaId='usmf',SalesOrderNumber='000002')/SalesOrderOrigin/$ref",
"SalesOrderOrigin@odata.navigationLink": "https://devaos.cloudax.dynamics.com/data/SalesOrderHeadersV2(dataAreaId='usmf',SalesOrderNumber='000002')/SalesOrderOrigin",
"Project@odata.associationLink": "https://devaos.cloudax.dynamics.com/data/SalesOrderHeadersV2(dataAreaId='usmf',SalesOrderNumber='000002')/Project/$ref",
"Project@odata.navigationLink": "https://devaos.cloudax.dynamics.com/data/SalesOrderHeadersV2(dataAreaId='usmf',SalesOrderNumber='000002')/Project",
"DimensionSet@odata.associationLink": "https://devaos.cloudax.dynamics.com/data/SalesOrderHeadersV2(dataAreaId='usmf',SalesOrderNumber='000002')/DimensionSet/$ref",
"DimensionSet@odata.navigationLink": "https://devaos.cloudax.dynamics.com/data/SalesOrderHeadersV2(dataAreaId='usmf',SalesOrderNumber='000002')/DimensionSet",
"QualityOrderHeaders@odata.associationLink": "https://devaos.cloudax.dynamics.com/data/SalesOrderHeadersV2(dataAreaId='usmf',SalesOrderNumber='000002')/QualityOrderHeaders/$ref",
"QualityOrderHeaders@odata.navigationLink": "https://devaos.cloudax.dynamics.com/data/SalesOrderHeadersV2(dataAreaId='usmf',SalesOrderNumber='000002')/QualityOrderHeaders",
"SalesOrderLines@odata.associationLink": "https://devaos.cloudax.dynamics.com/data/SalesOrderHeadersV2(dataAreaId='usmf',SalesOrderNumber='000002')/SalesOrderLines/$ref",
"SalesOrderLines@odata.navigationLink": "https://devaos.cloudax.dynamics.com/data/SalesOrderHeadersV2(dataAreaId='usmf',SalesOrderNumber='000002')/SalesOrderLines",
"SalesOrderLines": [
{
"@odata.type": "#Microsoft.Dynamics.DataEntities.SalesOrderLine",
"@odata.id": "https://devaos.cloudax.dynamics.com/data/SalesOrderLines(dataAreaId='usmf',InventoryLotId='000221')",
"@odata.etag": "W/\"JzAsNTYzNzE0NDU3NjswLDA7MCwwOzAsMDswLDA7MCwwJw==\"",
"@odata.editLink": "SalesOrderLines(dataAreaId='usmf',InventoryLotId='000221')",
"dataAreaId": "usmf",
"InventoryLotId": "000221",
...

"SalesOrderLineStatus": "Invoiced",
"ProjectCategoryId": "",
"ItemNumber": "D0001",
"DeliveryAddressDescription": "",
...
"SalesOrderNumber": "000002",
...

"RevRecContractStartDate@odata.type": "#DateTimeOffset",
"RevRecContractStartDate": "1900-01-01T12:00:00Z",
"SalesOrderHeaderV2@odata.associationLink": "https://devaos.cloudax.dynamics.com/data/SalesOrderLines(dataAreaId='usmf',InventoryLotId='000221')/SalesOrderHeaderV2/$ref",
"SalesOrderHeaderV2@odata.navigationLink": "https://devaos.cloudax.dynamics.com/data/SalesOrderLines(dataAreaId='usmf',InventoryLotId='000221')/SalesOrderHeaderV2",
"SalesOrderHeader@odata.associationLink": "https://devaos.cloudax.dynamics.com/data/SalesOrderLines(dataAreaId='usmf',InventoryLotId='000221')/SalesOrderHeader/$ref",
"SalesOrderHeader@odata.navigationLink": "https://devaos.cloudax.dynamics.com/data/SalesOrderLines(dataAreaId='usmf',InventoryLotId='000221')/SalesOrderHeader",
"DimensionSet@odata.associationLink": "https://devaos.cloudax.dynamics.com/data/SalesOrderLines(dataAreaId='usmf',InventoryLotId='000221')/DimensionSet/$ref",
"DimensionSet@odata.navigationLink": "https://devaos.cloudax.dynamics.com/data/SalesOrderLines(dataAreaId='usmf',InventoryLotId='000221')/DimensionSet",
"DimensionCombination@odata.associationLink": "https://devaos.cloudax.dynamics.com/data/SalesOrderLines(dataAreaId='usmf',InventoryLotId='000221')/DimensionCombination/$ref",
"DimensionCombination@odata.navigationLink": "https://devaos.cloudax.dynamics.com/data/SalesOrderLines(dataAreaId='usmf',InventoryLotId='000221')/DimensionCombination"
},
{
"@odata.type": "#Microsoft.Dynamics.DataEntities.SalesOrderLine",
"@odata.id": "https://devaos.cloudax.dynamics.com/data/SalesOrderLines(dataAreaId='usmf',InventoryLotId='000222')",
"@odata.etag": "W/\"JzAsNTYzNzE0NDU3NzswLDA7MCwwOzAsMDswLDA7MCwwJw==\"",
"@odata.editLink": "SalesOrderLines(dataAreaId='usmf',InventoryLotId='000222')",
"dataAreaId": "usmf",
"InventoryLotId": "000222",
...

},
{
"@odata.type": "#Microsoft.Dynamics.DataEntities.SalesOrderLine",
"@odata.id": "https://devaos.cloudax.dynamics.com/data/SalesOrderLines(dataAreaId='usmf',InventoryLotId='000223')",
"@odata.etag": "W/\"JzAsNTYzNzE0NDU3ODswLDA7MCwwOzAsMDswLDA7MCwwJw==\"",
"@odata.editLink": "SalesOrderLines(dataAreaId='usmf',InventoryLotId='000223')",
"dataAreaId": "usmf",
"InventoryLotId": "000223",
...

},
{
"@odata.type": "#Microsoft.Dynamics.DataEntities.SalesOrderLine",
"@odata.id": "https://devaos.cloudax.dynamics.com/data/SalesOrderLines(dataAreaId='usmf',InventoryLotId='000224')",
"@odata.etag": "W/\"JzAsNTYzNzE0NDU3OTswLDA7MCwwOzAsMDswLDA7MCwwJw==\"",
"@odata.editLink": "SalesOrderLines(dataAreaId='usmf',InventoryLotId='000224')",
"dataAreaId": "usmf",
"InventoryLotId": "000224",
...

},
{
"@odata.type": "#Microsoft.Dynamics.DataEntities.SalesOrderLine",
"@odata.id": "https://devaos.cloudax.dynamics.com/data/SalesOrderLines(dataAreaId='usmf',InventoryLotId='000225')",
"@odata.etag": "W/\"JzAsNTYzNzE0NDU4MDswLDA7MCwwOzAsMDswLDA7MCwwJw==\"",
"@odata.editLink": "SalesOrderLines(dataAreaId='usmf',InventoryLotId='000225')",
"dataAreaId": "usmf",
"InventoryLotId": "000225",
...
}
]
}




мы можем использовать дополнительные параметры в $expand запросе

GET https://[COLOR=var(--brand-primary)]{{base_url}}[/COLOR]/data/SalesOrderHeadersV2?$top=1&$select=SalesOrderNumber&$expand=SalesOrderLines($select=InventoryLotId,ItemNumber,SalesOrderNumber)


вернет


{
"@odata.context": "https://devaos.cloudax.dynamics.com/data/$metadata#SalesOrderHeadersV2(SalesOrderNumber,SalesOrderLines,SalesOrderLines(InventoryLotId,ItemNumber,SalesOrderNumber))",
"value": [
{
"@odata.etag": "W/\"JzAsNTYzNzE0NDU3NjswLDA7MCwwOzAsMDswLDA7MCwwJw==\"",
"SalesOrderNumber": "000002",
"SalesOrderLines": [
{
"@odata.etag": "W/\"JzAsNTYzNzE0NDU3NjswLDA7MCwwOzAsMDswLDA7MCwwJw==\"",
"InventoryLotId": "000221",
"ItemNumber": "D0001",
"SalesOrderNumber": "000002"
},
{
"@odata.etag": "W/\"JzAsNTYzNzE0NDU3NzswLDA7MCwwOzAsMDswLDA7MCwwJw==\"",
"InventoryLotId": "000222",
"ItemNumber": "L0001",
"SalesOrderNumber": "000002"
},
{
"@odata.etag": "W/\"JzAsNTYzNzE0NDU3ODswLDA7MCwwOzAsMDswLDA7MCwwJw==\"",
"InventoryLotId": "000223",
"ItemNumber": "P0001",
"SalesOrderNumber": "000002"
},
{
"@odata.etag": "W/\"JzAsNTYzNzE0NDU3OTswLDA7MCwwOzAsMDswLDA7MCwwJw==\"",
"InventoryLotId": "000224",
"ItemNumber": "D0003",
"SalesOrderNumber": "000002"
},
{
"@odata.etag": "W/\"JzAsNTYzNzE0NDU4MDswLDA7MCwwOzAsMDswLDA7MCwwJw==\"",
"InventoryLotId": "000225",
"ItemNumber": "D0004",
"SalesOrderNumber": "000002"
}
]
}
]
}



cross-company=true


По усолчанию запросы выполняются к компании в которой по умолчанию привязан пользователь ассоциированный с парой clientId, secret Id


Для того что бы выполнить запрос по всем компаниям компаниям, необходимо добавить параметр cross-company=true






Вставка записей (Insert/POST)


Вставка осуществляется при помощи запроса POST


POST
https://[COLOR=var(--brand-primary)]{{base_url}}[/COLOR]/data/CustomerGroups





Закладка Body


{
"@odata.type":"#Microsoft.Dynamics.DataEntities.CustomerGroup",
"CustomerGroupId":"T1",
"dataAreaId":"usmf",
"Description":"Test group T1"
}



Где

@odata.type - тип записи который мы вставляем. Очень грубо говоря - Entity в которую мы осуществляем вставку называется CustomerGroups, мы в нее вставляемзапись типа #Microsoft.Dynamics.DataEntities.CustomerGroup. Внешне энтити и тип помут быть похожи, а могут быть не похожи.

Заполняем поля - поля со звездочной - обязательные для заполнения поля

* "CustomerGroupId":"T1",
* "dataAreaId":"usmf",
"Description":"Test group T1"



Выполняем запрос, получаем ответ








Статус ответа 201 Created - запись создана


Выполняем запрос с фильтром на выборку и проверяем


GET
https://[COLOR=var(--brand-primary)]{{base_url}}[/COLOR]/data/CustomerGroups?$filter=CustomerGroupId eq 'T1'


{
"@odata.context": "https://devaos.cloudax.dynamics.com/data/$metadata#CustomerGroups",
"value": [
{
"@odata.type": "#Microsoft.Dynamics.DataEntities.CustomerGroup",
"@odata.id": "https://devaos.cloudax.dynamics.com/data/CustomerGroups(dataAreaId='usmf',CustomerGroupId='T1')",
"@odata.etag": "W/\"JzEsNjg3MTk0ODI4MzMn\"",
"@odata.editLink": "CustomerGroups(dataAreaId='usmf',CustomerGroupId='T1')",
"dataAreaId": "usmf",
"CustomerGroupId": "T1",
"ClearingPeriodPaymentTermName": "",
"CustomerAccountNumberSequence": "",
"DefaultDimensionDisplayValue": "",
"Description": "Test group T1",
"IsSalesTaxIncludedInPrice@odata.type": "#Microsoft.Dynamics.DataEntities.NoYes",
"IsSalesTaxIncludedInPrice": "No",
"WriteOffReason": "",
"PaymentTermId": "",
"TaxGroupId": "",
"DimensionSet@odata.associationLink": "https://devaos.cloudax.dynamics.com/data/CustomerGroups(dataAreaId='usmf',CustomerGroupId='T1')/DimensionSet/$ref",
"DimensionSet@odata.navigationLink": "https://devaos.cloudax.dynamics.com/data/CustomerGroups(dataAreaId='usmf',CustomerGroupId='T1')/DimensionSet",
"Prospects@odata.associationLink": "https://devaos.cloudax.dynamics.com/data/CustomerGroups(dataAreaId='usmf',CustomerGroupId='T1')/Prospects/$ref",
"Prospects@odata.navigationLink": "https://devaos.cloudax.dynamics.com/data/CustomerGroups(dataAreaId='usmf',CustomerGroupId='T1')/Prospects"
}
]
}




Обновление записей (Update/PUT)


PUT
https://[COLOR=var(--brand-primary)]{{base_url}}[/COLOR]/data/CustomerGroups(dataAreaId='usmf',CustomerGroupId='T1')


Обратите внимание - мы должны четко выбрать одну записьпо ключу, и только тогда ее обновить.


Headers - такой же


Body
{
"@odata.type":"#Microsoft.Dynamics.DataEntities.CustomerGroup",
"Description":"Test group T123"
}


Ответ Status 204 No content






проверяем ззапросом на выборку



GET
https://[COLOR=var(--brand-primary)]{{base_url}}[/COLOR]/data/CustomerGroups?$filter=CustomerGroupId eq 'T1'


{
"@odata.context": "https://devaos.cloudax.dynamics.com/data/$metadata#CustomerGroups",
"value": [
{
"@odata.etag": "W/\"JzM0NjU2NTI5MCw2ODcxOTQ4MjgzMyc=\"",
"dataAreaId": "usmf",
"CustomerGroupId": "T1",
"ClearingPeriodPaymentTermName": "",
"CustomerAccountNumberSequence": "",
"DefaultDimensionDisplayValue": "",
"Description": "Test group T123",
"IsSalesTaxIncludedInPrice": "No",
"WriteOffReason": "",
"PaymentTermId": "",
"TaxGroupId": ""
}
]
}



Удаление записей (Delete/DELETE)





Заголовок тот же, боди -пустое, запрос DELETE
https://[COLOR=var(--brand-primary)]{{base_url}}[/COLOR]/data/CustomerGroups(dataAreaId='usmf',CustomerGroupId='T1')


Ответ




Статус 204 - No Content


проверяем
GET
https://[COLOR=var(--brand-primary)]{{base_url}}[/COLOR]/data/CustomerGroups?$filter=CustomerGroupId eq 'T1'





Нет записей для отображения, мы справились


Транзакция ttsbegin..ttscommit BATCH

Для того, что бы выполнить вставку многих записей в один раз мы должны выполнить запрос к сервису batch


POST
https://[COLOR=var(--brand-primary)]{{base_url}}[/COLOR]/data/$batch


В Header:



Content-Type:
multipart/mixed; boundary=batch_boundary

Body:



--batch_boundary
Content-Type: multipart/mixed; boundary=changeset_boundary

--changeset_boundary
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 1

POST https://{{base_url}}/data/CustomerGroups HTTP/1.1
OData-Version: 4.0
OData-MaxVersion: 4.0
Content-Type: application/json;odata.metadata=minimal
Accept: application/json;odata.metadata=minimal
Accept-Charset: UTF-8

{
"@odata.type":"#Microsoft.Dynamics.DataEntities.CustomerGroup",
"CustomerGroupId":"Batch1",
"dataAreaId":"usmf",
"Description":"Batch 1 Test group T1"
}

--changeset_boundary
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 2

POST https://{{base_url}}/data/CustomerGroups HTTP/1.1
OData-Version: 4.0
OData-MaxVersion: 4.0
Content-Type: application/json;odata.metadata=minimal
Accept: application/json;odata.metadata=minimal
Accept-Charset: UTF-8

{
"@odata.type":"#Microsoft.Dynamics.DataEntities.CustomerGroup",
"CustomerGroupId":"Batch2",
"dataAreaId":"usmf",
"Description":"Batch 2 Test group T1"
}

--batch_boundary--




Если по какой-то причине какая-то запись не вставилась - сервис batch вернет статус 200 - что он отработал нормально. Другое дело, при вызове multipart нужно анализировать все ответы.


--batchresponse_5a294678-02dd-4b16-94b9-5fd64e297a8e
Content-Type: multipart/mixed; boundary=changesetresponse_1209a811-16fe-40f4-98e9-5519c744549e

--changesetresponse_1209a811-16fe-40f4-98e9-5519c744549e
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 1

HTTP/1.1 500 Internal Server Error
Content-Type: application/json; odata.metadata=minimal; charset=utf-8
OData-Version: 4.0

{
"error":{
"code":"","message":"An error has occurred.","innererror":{
"message":"Write failed for table row of type 'CustCustomerGroupEntity'. Infolog: Info: Cannot create a record in Customer groups (CustGroup). Customer group: Batch2, Batch 2 Test group T1.\nThe record already exists..","type":"Microsoft.Dynamics.Platform.Integration.Services.OData.AxODataWriteException","stacktrace":" at Microsoft.Dynamics.Platform.Integration.Services.OData.Update.UpdateProcessor.CreateEntity_Save(ChangeOperationContext context, ChangeInfo changeInfo)\r\n at Microsoft.Dynamics.Platform.Integration.Services.OData.Update.UpdateManager.c__DisplayClass6_0.b__1(ChangeOperationContext context)\r\n at Microsoft.Dynamics.Platform.Integration.Services.OData.Update.ChangeInfo.ExecuteActionsInCompanyContext(IEnumerable`1 actionList, ChangeOperationContext operationContext)\r\n at Microsoft.Dynamics.Platform.Integration.Services.OData.Update.ChangeInfo.TrySave(ChangeOperationContext operationContext)\r\n at Microsoft.Dynamics.Platform.Integration.Services.OData.Update.UpdateManager.SaveChanges()\r\n at Microsoft.Dynamics.Platform.Integration.Services.OData.AxODataBatchHandler.d__8.MoveNext()"
}
}
}
--changesetresponse_1209a811-16fe-40f4-98e9-5519c744549e--
--batchresponse_5a294678-02dd-4b16-94b9-5fd64e297a8e--



Номерные серии

Работа с Entity позволяет получать автозаполнение полей стандартным способом. Например при вставке строк заказов на продажу достаточно заполнить всего пару полей в POST запросе


{
"@odata.type":"#Microsoft.Dynamics.DataEntities.SalesOrderLine",
"ItemNumber":"D0001",
"SalesOrderNumber":"001261"
}


Строка успешно создастся и поля единица измерения, кол-во, цена, сумма ... аналитики - заполнятся по умолчанию.



В случае вставки заказа на продажу (шапка) та же история
{
"@odata.type":"#Microsoft.Dynamics.DataEntities.SalesOrderHeaderV2",
"CurrencyCode": "USD",
"InvoiceCustomerAccountNumber":"US-001",
"OrderingCustomerAccountNumber": "US-001",
"dataAreaId":"usmf"
}



При этом номер заказа для шапки будет сгенерирован автоматически.


Что нам делать, если мы попробуем сделать вставку Шапки заказа и строк в одной транзакции при помощи batch?



Нам нужно что бы шапка и строки были с одним номером. Для этого у нас есть два варианта. Первый - нужно присвоить следующий номер или произвольный( в случае с произвольным номером - номерная серия для номера заказа на продажу должна допускать редактирование).

Заключение




В принципе мы рассмотрели возможности применения данной технологии. Это не единственная технология интеграции в аксапте, но точно очень полезная. Понимая эти принципы - можно дальше идти с интеграцией в MS Power Automate.




Источник: http://littleax.blogspot.com/2020/06...f-d365scm.html
__________________
Расскажите о новых и интересных блогах по Microsoft Dynamics, напишите личное сообщение администратору.