Using data contracts it is possible to build an arbitraty structure of input arguments and return values. For example, we need to update in one request several records and receive a detailed response about success of an operation for each record.
As an input we will receive a list of record identifiers with parameters to be changed
as an output user can receive a list of operation results for each record.
Following steps need to be performed:
Create data contracts for input and output and build a needed tree.
Create a custom service.
Add service to a service group.
Build module.
Run a request with a RESTful services tool (Postman.)
As an example we will take a sales order with sales order lines. Confirmed shupping date and quantity should be updated on each line.
Input contract:
/// <summary>
/// Data contract for sales order Line
/// </summary>
[DataContract]
class ruaSalesOrderLineContract
{
InventTransId inventTransId;
SalesQty salesQty;
SalesShippingDateConfirmed shippingDateConfirmed;
[DataMember('InventTransId')]
public InventTransId parmInventTransId(InventTransId _inventTransId = inventTransId)
{
inventTransId = _inventTransId;
return inventTransId;
}
[DataMember('SalesQty')]
public SalesQty parmSalesQty(SalesQty _salesQty = salesQty)
{
salesQty = _salesQty;
return salesQty;
}
[DataMember('ShippingDateConfirmed')]
public SalesShippingDateConfirmed parmShippingDateConfirmed(SalesShippingDateConfirmed _shippingDateConfirmed = shippingDateConfirmed)
{
shippingDateConfirmed = _shippingDateConfirmed;
return shippingDateConfirmed;
}
}
====================================================================
This contract will be an item in a list of an input contract for a sales order:
/// <summary>
/// Data contract for sales order service
/// </summary>
[DataContract]
class ruaSalesOrderHeaderContract
{
SalesId salesId;
List salesLineList = new List(Types::Class);
/// <summary>
/// Order ID
/// </summary>
/// <param name = "_salesId">Order ID</param>
/// <returns></returns>
[DataMember('SalesId')]
public SalesId parmSalesId(SalesId _salesId = salesId)
{
salesId = _salesId;
return salesId;
}
/// <summary>
/// Get/set list of lines
/// </summary>
/// <param name = "_salesLineList">Lines list</param>
/// <returns></returns>
[DataMember('Lines'),
AifCollectionType('_salesLineList', Types::Class, classStr(ruaSalesOrderLineContract)),
AifCollectionType('return', Types::Class, classStr(ruaSalesOrderLineContract))]
public List parmLines(List _salesLineList = salesLineList)
{
salesLineList = _salesLineList;
return salesLineList;
}
}
===========================================================
Then we will define a response.
First we will create a response data contract for a sales line:
/// <summary>
/// Response object for updating/inserting sales lines
/// </summary>
[DataContract]
class ruaSalesOrderLineResponseContract
{
InventTransId inventTransId;
str message;
str status;
[DataMember("InventTransId")]
public InventTransId parmInventTransId(InventTransId _inventTransId = inventTransId)
{
inventTransId = _inventTransId;
return inventTransId;
}
[DataMember("Status")]
public str parmStatus(str _status = status)
{
status = _status;
return status;
}
[DataMember("Message")]
public str parmMessage(str _message = message)
{
message = _message;
return message;
}
public void setFailed(str _message)
{
this.parmStatus("Failed");
this.parmMessage(_message);
}
public void setSuccess(str _message = "success")
{
this.parmStatus("OK");
this.parmMessage(_message);
}
}
=====================================================================
Then a response contract for a service method. This method will contain a list of a sales lines' responses:
[DataMember('Lines'),
AifCollectionType('_linesResponse', Types::Class, classStr(ruaSalesOrderLineResponseContract)),
AifCollectionType('return', Types::Class, classStr(ruaSalesOrderLineResponseContract))]
public List parmLines(List _linesResponse = linesResponse)
{
linesResponse = _linesResponse;
return linesResponse;
}
==========================================================
A response contract looks like this:
/// <summary>
/// COntract class for sales order service
/// </summary>
[DataContract]
class ruaSalesOrderServiceResponseContract
{
SalesId salesId;
str message;
str status;
List linesResponse = new List(Types::Class);
[DataMember("SalesId")]
public SalesId parmSalesId(SalesId _salesId = salesId)
{
salesId = _salesId;
return salesId;
}
[DataMember("Status")]
public str parmStatus(str _status = status)
{
status = _status;
return status;
}
[DataMember("Message")]
public str parmMessage(str _message = message)
{
message = _message;
return message;
}
[DataMember("Lines"),
AifCollectionType("_linesResponse", Types::Class, classStr(ruaSalesOrderLineResponseContract)),
AifCollectionType("return", Types::Class, classStr(ruaSalesOrderLineResponseContract))]
public List parmLines(List _linesResponse = linesResponse)
{
linesResponse = _linesResponse;
return linesResponse;
}
public void setFailed(str _message)
{
this.parmStatus("Failed");
this.parmMessage(_message);
}
public void setSuccess(str _message = "success")
{
this.parmStatus("OK");
this.parmMessage(_message);
}
}
=======================================================
Then we will create a service with 1 method updateOrderLines
/// <summary>
/// Service class for processing sales order in one transaction
/// </summary>
class ruaSalesOrderService extends SysOperationServiceBase
{
/// <summary>
/// Updates sales order lines
/// </summary>
/// <param name = "_headerContract">Sales order header contract (with lines)</param>
/// <returns>Response for header and each line</returns>
public ruaSalesOrderServiceResponseContract updateOrderLines(ruaSalesOrderHeaderContract _headerContract)
{
SalesLine salesLine;
ListEnumerator salesLineListEnumerator;
ruaSalesOrderLineContract salesOrderLineContract;
ruaSalesOrderServiceResponseContract response = new ruaSalesOrderServiceResponseContract();
boolean ok = true, isModified;
try
{
if(_headerContract == null)
{
response.setFailed("Input contract is null");
return response;
}
if(!SalesTable::exist(_headerContract.parmSalesId()))
{
response.setFailed(strFmt("Sales order %1 does not exist", _headerContract.parmSalesId()));
return response;
}
if(_headerContract.parmLines() == null || _headerContract.parmLines().empty())
{
response.setFailed("Input contract contains no lines");
return response;
}
salesLineListEnumerator = _headerContract.parmLines().getEnumerator();
ttsbegin;
response.parmSalesId(_headerContract.parmSalesId());
while(salesLineListEnumerator.moveNext())
{
isModified = false;
salesOrderLineContract = salesLineListEnumerator.current();
if(salesOrderLineContract)
{
ruaSalesOrderLineResponseContract lineResponse = new ruaSalesOrderLineResponseContract();
lineResponse.parmInventTransId(salesOrderLineContract.parmInventTransId());
response.parmLines().addEnd(lineResponse);
salesLine = SalesLine::findInventTransId(salesOrderLineContract.parmInventTransId(), true);
if(!salesLine)
{
lineResponse.setFailed(strFmt("Sales line with ID %1 was not found", salesOrderLineContract.parmInventTransId()));
ok = false;
continue;
}
if(salesLine.SalesId != _headerContract.parmSalesId())
{
lineResponse.setFailed(strFmt("Sales line with ID %1 is from another sales order (%2)", salesOrderLineContract.parmInventTransId(), salesLine.SalesId));
ok = false;
continue;
}
if(salesOrderLineContract.parmSalesQty() != 0 && salesOrderLineContract.parmSalesQty() != salesLine.SalesQty)
{
salesLine.SalesQty = salesOrderLineContract.parmSalesQty();
salesLine.modifiedField(fieldNum(SalesLine, SalesQty));
isModified = true;
}
if(salesOrderLineContract.parmShippingDateConfirmed() != dateNull() && salesOrderLineContract.parmShippingDateConfirmed() != salesLine.ShippingDateConfirmed)
{
salesLine.ShippingDateConfirmed = salesOrderLineContract.parmShippingDateConfirmed();
salesLine.modifiedField(fieldNum(SalesLine, ShippingDateConfirmed));
isModified = true;
}
if(isModified)
{
if(!salesLine.validateWrite())
{
lineResponse.setFailed(ruaWebServiceHelper::getErrorFromInfolog());
ok = false;
}
else
{
lineResponse.setSuccess();
salesLine.update();
}
}
else
{
lineResponse.setSuccess("No changes are acknowledged");
}
infolog.clear();
}
}
if(!ok)
{
throw error("@ElectronicReportingMapping:ValidationErrorsExist");
}
response.setSuccess();
ttscommit;
}
catch(Exception::CLRError)
{
response.setFailed(AifUtil::getClrErrorMessage());
}
catch(Exception::Error)
{
response.setFailed(ruaSalesOrderService::getErrorFromInfolog());
}
return response;
}
public static str getErrorFromInfolog()
{
SysInfologEnumerator enumerator;
SysInfologMessageStruct msgStruct;
Exception exception;
str error;
enumerator = SysInfologEnumerator::newData(infolog.cut());
while (enumerator.moveNext())
{
msgStruct = new SysInfologMessageStruct(enumerator.currentMessage());
exception = enumerator.currentException();
error = strfmt("%1 %2", error, msgStruct.message());
}
return error;
}
}
=========================================================================
Create a new service ruaSalesOrderService.
Add operation updateOrderLines to it.
Create new service group ruaServices
Add ruaSalesOrderService to this group.
To call a D365FO service from extern first we need to obtain an authorization token.
A detailed descriptions can be found here.
Service URL should look like this:
https://<your_instance>.cloudax.dynamics.com/api/services/<Service group name>/ruaSalesOrderService/updateOrderLines
===========================================================================
A request JSON names should exactly coinside with a parameter names:
{
"_headerContract":
{
"SalesId":"RUA-S000093",
"Lines":[
{
"InventTransId":"SOL-000562",
"ShippingDateConfirmed":"2019-09-28T18:25:43.511Z",
"SalesQty":6
},
{
"InventTransId":"SOL-000611",
"ShippingDateConfirmed":"2019-09-28T18:25:43.511Z",
"SalesQty":14
}]
}
}
===============================================================
A response will look like this:
{
"$id": "1",
"SalesId": "RUA-S000093",
"Status": "Failed",
"Message": " Validation errors exist",
"Lines": [
{
"$id": "2",
"InventTransId": "SOL-000562",
"Status": "Failed",
"Message": "Sales line with ID IFR-000504 was not found"
},
{
"$id": "3",
"InventTransId": "SOL-000611",
"Status": "Success",
"Message": ""
}
]
}
===========================================================================================================
Comments
Post a Comment