23.06.2020, 14:12 | #1 |
Участник
|
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 Возможность выбирать данные по ключу - одну запись. Очень важное свойство в операциях PATCH, PUT, DELETEсортируем записи, по умолчанию 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 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" } ] } ] } По усолчанию запросы выполняются к компании в которой по умолчанию привязан пользователь ассоциированный с парой 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_boundaryBody: --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, напишите личное сообщение администратору. |
|
Теги |
d365fo |
|
|