How to create entry for 2 or more entity sets via OdataModel.create in UI5 - odata

I have a Odata service with 4 entity types - Address, CC, Header, Item, Comments.
After the user presses the "Order" button, I want to create an entry in backend using this service. There is a header and associated items that I need to pass.
oData Service from backend:
{
"d" : {
"__metadata" : {
"id" : "http://.../sap/opu/odata/sap/ZOrders/HeaderSet('3898')",
"uri" : "http://.../sap/opu/odata/sap/ZOrders/HeaderSet('3898')",
"type" : "ZOrder.Header"
},
"CompanyID" : "W",
"StockRoomID" : "A",
"SalesDocument" : "3898",
"ItemSet" : [
{
"__metadata" : {
"id" : "http://.../sap/opu/odata/sap/ZOrders/ItemSet(SalesDocument='3898',SalesDocumentItem='000010')",
"uri" : "http://.../sap/opu/odata/sap/ZOrders/ItemSet(SalesDocument='3898',SalesDocumentItem='000010')",
"type" : "ZOrders.Item"
},
"SalesDocument" : "3898",
"SalesDocumentItem" : "000010", //Line item number
"StockRoomID" : "A",
}
]
}
}
Controller.js
buttonClick: function(event) {
var sServiceURl = this.getOwnerComponent().getMetadata().getManifestEntry("sap.app").dataSources["ZOrders"].uri;
this.OdataModel = new sap.ui.model.odata.v2.ODataModel(sServiceURl);
var model = vc.getView().getModel();
var oEntry = {};
oEntry.CompanyID = model.getProperty("/CompanyID");
oEntry.StockRoomID = model.getProperty("/StockRoomID");
oEntry.SalesDocument = model.getProperty("/SalesDocument");
//Now want to pass items data to service so
//Creating an empty ItemSet array, pulling data from model.
//But my logic is wrong here for sure.
var itemData = [];
var itemsArray = v.getProperty("/ItemSet");
for (var i = 0; i < itemsArray.results.length; i++) {
itemData.push({
SalesDocument: itemsArray.results[i].SalesDocument,
SalesDocumentItem: itemsArray.results[i].SalesDocumentItem,
StockRoomID: itemsArray.results[i].StockRoomID,
});
}
oEntry.ItemSet = itemData;
this.OdataModel.create("/HeaderSet", oEntry, this._submitOrderSuccess.bind(this), this._submitOrderError.bind(this));
**Debugger - Header payload **
{
"CompanyID":"RSW0",
"StockRoomID":"A200",
"SalesDocument":"4053",
"Return":"X",
"Reason":"101",
"ItemSet":[
{
"SalesDocument":"4053",
"SalesDocumentItem":"000010",
"StockRoomID":"A200",
"ReturnItemFlag":"X",
"QtyToReturn":"1.000"
},
{
"SalesDocument":"4053",
"SalesDocumentItem":"000020",
"StockRoomID":"A200",
"ReturnItemFlag":"X",
"QtyToReturn":"1.000"
},
{
"SalesDocument":"4053",
"SalesDocumentItem":"000030",
"StockRoomID":"A200",
"ReturnItemFlag":"X",
"QtyToReturn":"1.000"
}
]
}
**Debugger - Response **
{
"d":{
"__metadata":{
"id":"https://.../sap/opu/odata/sap/ZORDER/HeaderSet('60000353')",
"uri":"https://.../sap/opu/odata/sap/ZORDER/HeaderSet('60000353')",
"type":"ZORDER.Header"
},
"CompanyID":"",
"StockRoomID":"",
"SalesDocument":"60000353",
"ReferenceDoc":"",
"AccountNumber":"",
"PoNumber":"",
"Message":"Return order 0060000353 has been created successfully",
"OrderTotal":"0.00",
"StockRoomName":"",
"Return":"",
"Reason":"",
"Auth":"",
"ItemSet":null,
"HeaderCSDSet":{
"__deferred":{
"uri":"https://.../sap/opu/odata/sap/ZORDER/HeaderSet('60000353')/HeaderCSDSet"
}
},
"AddressSet":{
"__deferred":{
"uri":"https://.../sap/opu/odata/sap/ZORDER/HeaderSet('60000353')/AddressSet"
}
},
"GeneralCommentsSet":{
"__deferred":{
"uri":"https://.../sap/opu/odata/sap/ZORDER/HeaderSet('60000353')/GeneralCommentsSet"
}
},
"CreditCardSet":{
"__deferred":"uri":"https://.../sap/opu/odata/sap/ZORDER/HeaderSet('60000353')/CreditCardSet"
}
}
}
}

This is a scenario of dependant Entity or Navigation property from Header to Item. Google "Deep Entity in SAPUI5 and oData for more understanding".
There is an error in your code where you have mentioned: "the logic is wrong".
Just add one line after the for loop:
oEntry.Items = ItemSet;
Let me know if this helps.

Related

Returning recursive result

I currently have this in a simple MVC Api controller:
var rootFolder = Umbraco.TypedMedia(200);
return rootFolder.Children().Select(s => new MediaItem
{
Name = s.Name,
Children = s.Children.Select(e => new MediaItem
{
Name = e.Name
})
});
It works, but only return level 1 and 2.
I tried using
return rootFolder.Descendants(), which returns all results from all levels - but "flattened out", so I cannot see the structure in the output.
The output is used in a simple app, navigating a tree structure.
Any ideas, as to how I can make it recursive?
Using Descendants, the output is returned like this
[
{
"Name":"dok1"
},
{
"Name":"dok2"
},
{
"Name":"dok21"
}
]
But it should be
[
{
"Name":"dok1"
},
{
"Name":"dok2"
"Children": [
{
"Name":"dok21"
}
]
}
Not sure you really need recursion here -- the solution below (or something similar) should suffice
// Dictionary between level/depth(int) and the files on that level/depth
var fileDictionary = new Dictionary<int, List<MediaItem>>();
var children = rootFolder.Children();
var depth = 1;
while (children.Any())
{
var tempList = new List<MediaItem>();
children.ForEach(child => {
tempList.Add(child);
});
fileDictionary.Add(depth, tempList);
children = children.Children();
depth++;
}
Then, you can do something like:
foreach (var key in fileDictionary.Keys)
{
// Access the key by key.Key (key would be "depth")
// Access the values by fileDictionary[key] (values would be list of MediaItem)
}
Why not just create a recursive function like so?
IEnumerable<MediaItem> ConvertToMediaItems(IEnumerable<IPublishedContent> items)
{
return items?.Select(i => new MediaItem
{
Name = i.Name,
Children = ConvertToMediaItems(i.Children)
}) ?? Enumerable.Empty<MediaItem>();
}
Then the usage would be
var rootFolder = Umbraco.TypedMedia(200);
return ConvertToMediaItems(rootFolder.Children());
You can also make the function a local function if it's only needed in one place.

deleting a dictionary record in a SwiftyJSON array

Swift 3.0 iOS 10.x SwiftyJSON
I have a SwiftyJSON array of dictionary objects that looks like this ...
[
{
"figure" : 1326,
"account" : "Charles"
}
{
"figure" : 2361,
"account" : "James"
}
]
I want to delete the record within it belonging to "James", and I came up with this code. jsonObjects contains the array you see above.
var json2S = sharedDataAccess.jsonObjects
for json2DX in 0..<json2S.count {
var delIndex: Int? = nil
for (jsonK, subJson) in sharedDataAccess.jsonObjects[json2DX] {
print("json2D \(jsonK) \(subJson.stringValue) \(Name)")
if jsonK == "account" && subJson.stringValue != Name {
delIndex = json2DX
print("DELETE IT \(jsonK) \(subJson.stringValue) \(Name)")
}
}
if delIndex != nil {
json2S[0].arrayObject?.remove(at: delIndex!)
print("DELETING IT \(delIndex) \(Name)")
}
}
sharedDataAccess.jsonObjects = JSON(json2S)
It works, but not quite the way I had hoped. It deletes James [assuming Name variable contains James], but it leaves me with this.
[
null,
{
"figure" : 1326,
"account" : "Charles"
}
]
James is replaced with null... A null I don't want, how can I delete entries but not get a null or indeed just delete the null too!!
Ok, here is my answer. Posted it for good measure; not quite the same logic but followed Vadian's point.
var json2S = sharedDataAccess.jsonObjects
var jsonA:[[String:Any]] = []
for json2P in sharedDataAccess.jsonObjects {
print("json2P \(json2P)")
var jsonDict:[String:Any] = [:]
var json2Save = false
for (jsonK, subJson) in json2P {
print("jsonK \(jsonK) \(subJson.stringValue) \(fnName)")
jsonDict[jsonK] = subJson.stringValue
if jsonK == "account" && subJson.stringValue == fnName {
json2Save = true
}
}
if json2Save {
jsonA.append(jsonDict)
}
}
let sharedDataAccess.jsonObjects = JSON(jsonA)

unable to upload files using UploadCollection in SAPUI5

I am receiving 403 Forbidden error when I try to upload a file using UploadCollection.
The code in my view.js is:
var oOUpload = new sap.m.UploadCollection("oinspupload",{
multiple : true,
sameFilenameAllowed : true,
instantUpload : false,
uploadUrl : "/sap/opu/odata/sap/ZACCBILL_SRV/FileSet",
/* uploadComplete : function(oEvent){
//alert ("File Uploaded successfully");
// oController.fileUpload(oEvent);
}, */
fileDeleted : function(oEvent){
oController.fileDelete(oEvent);
},
fileRenamed : function(oEvent){
alert ("File renamed successfully");
//oController.fileRename(oEvent);
}
});
The code in my view.controller is:
OData.request({
requestUri : sServiceUrl,
method : "GET",
headers :
{
"X-Requested-With" : "XMLHttpRequest",
"Content-Type" : "application/atom+xml",
"DataServiceVersion" : "2.0",
"Authorization" : AuthToken,
"X-CSRF-Token" : "Fetch"
}
},
function(data, response) {
debugger;
if(sap.ui.Device.browser.chrome || sap.ui.Device.browser.msie || sap.ui.Device.browser.safari){
header_xcsrf_token = response.headers['x-csrf-token'];
}else if(sap.ui.Device.browser.firefox){
header_xcsrf_token = response.headers['X-CSRF-Token'];
}
xcsrf_token_ref.header_xcsrf_token = header_xcsrf_token;
csrftoken = xcsrf_token_ref.header_xcsrf_token;
debugger;
uploadattachments(xcsrf_token_ref);
},
function(err) {
debugger;
var request = err.request; // the request that was sent.
var response = err.response; // the response that was received.
alert("Error in Get -- Request "
+ request + " Response "
+ response);
});
function uploadattachments(token){
debugger;
var uploader;
uploader= sap.ui.getCore().byId("oinspupload");
var aItems = uploader.getItems();
var slug, sequence;
for (i = 0; i < aItems.length; i++) {
sequence = i + 1;
slug = "CONTAINERID1000040100;STATUSIB;SEQUENCE" + sequence+ ";FILENAMECamera.png" ;
uploader.addHeaderParameter(new sap.m.UploadCollectionParameter({name: "slug", value: slug }));
debugger;
uploader.addHeaderParameter(new sap.m.UploadCollectionParameter({name: "X-Csrf-Token", value: token.header_xcsrf_token }));
uploader.upload();
}
}
Please don't mind the missing parenthesis as the code above is not the complete code.
The above code works fine with fileuploader. I am sure the issue is that the uploadcollection is not passing the fetched CSRF Token properly but I am unable to figure out what's wrong.
Finally Found the solution myself with the help of the following blog
http://scn.sap.com/community/developer-center/front-end/blog/2016/03/29/using-the-uploadcollection-to-uploaddownload-archivelink-files-via-gateway
Upload Collection only works with instantUpload as true and does not work with instantUpload as false as of version 1.32.X. UploadCollection is Buggy and is yet to be rectified in the future versions. Also the CSRF token validation needs to be done in the change event. Below is the code:
View.js
var oOUpload = new sap.m.UploadCollection("oinspupload",{
multiple : true,
sameFilenameAllowed : false,
instantUpload : true,
uploadUrl : "/sap/opu/odata/sap/ZACCBILL_SRV/FileSet",
fileDeleted : function(oEvent){
oController.fileDelete(oEvent);
},
fileRenamed : function(oEvent){
alert ("File renamed successfully");
},
change: function(oEvent) {
debugger;
csrftoken = xcsrf_token_ref.header_xcsrf_token;
var oUploadCollection = oEvent.getSource();
var oCustomerHeaderToken = new sap.m.UploadCollectionParameter({
name : "x-csrf-token",
value : csrftoken
});
oUploadCollection.addHeaderParameter(oCustomerHeaderToken);
},
});
All header params must be added from "change" function. If you add it after, they won't be recieved on Backend.
Also, It is possible upload files with instantUpload=false. You only need bind uploadUrl parameter with a paremeter of view's model, and dynamically, it will change when you change the url.
For example:
View element:
<UploadCollection instantUpload="false" uploadUrl="{ResourceModel>/sServiceUrl}"/>
Controller onInitFunction:
var resourcemodel = this.getOwnerComponent().getModel("ZGW_PURCHREQ_01_SRV");
var oDataResource = {
sServiceUrl: resourcemodel.sServiceUrl + "/FileSet"
};
var jsonResource = new JSONModel(oDataResource);
this.getView().setModel(jsonResource, "ResourceModel");
When you fire upload, it will send a petition to uploadUrl defined on "sServiceUrl" of "ResourceModel".
Other option is set upload url and/or new header params before fire upload function with:
var oUploadCollection = this.getView().byId("UploadCollection");
var sServiceUrl = resourcemodel.sServiceUrl + "/FileSet";
var headerBanfn = null;
for (var i = 0; i < oUploadCollection._aFileUploadersForPendingUpload.length; i++) {
headerBanfn = new sap.ui.unified.FileUploaderParameter({
name: "banfn",
value: "123456"
});
oUploadCollection._aFileUploadersForPendingUpload[i].setUploadUrl(sServiceUrl);
oUploadCollection._aFileUploadersForPendingUpload[i].addHeaderParameter(headerBanfn);
}
oUploadCollection.upload();
I hope it was useful.

Are the yahoo.finance related data api's change (YQL Console)?

I am trying to use yql for yahoo financial data. I checked the Show Community Table on the YQL console to see the database under the Yahoo tag.
I can see the tables under it but i am not getting results
here it is as follows:::
select * from yahoo.finance.analystestimate where symbol in ('YHOO')
{
"query": {
"count": 1,
"created": "2016-03-28T10:25:01Z",
"lang": "en-US",
"diagnostics": {
"url": [
{
"execution-start-time": "1",
"execution-stop-time": "767",
"execution-time": "766",
"content": "http://www.datatables.org/yahoo/finance/yahoo.finance.analystestimate.xml"
},
{
"execution-start-time": "771",
"execution-stop-time": "1821",
"execution-time": "1050",
"content": "http://finance.yahoo.com/q/ae?s=YHOO"
}
],
"publiclyCallable": "true",
"javascript": {
"execution-start-time": "769",
"execution-stop-time": "1823",
"execution-time": "1054",
"instructions-used": "5139",
"table-name": "yahoo.finance.analystestimate"
},
"user-time": "1824",
"service-time": "1806",
"build-version": "0.2.842"
},
"results": {
"results": {
"symbol": "YHOO"
}
}
}
}
here results are shown as empty ..
Has something changed? How can I find out what happened?
Is there an alternative solution I can use to obtain this data?
The JS the developer used to create the table us no longer working. This is it partially formatted. You can see that he's grabbing the page and then screen scraping it.
function getelement(row) {
if (row.hasOwnProperty("p")) return (row.p.text());
return (row.font.text());
} // Setup Query from finance.yahoo.com
var url = "http://finance.yahoo.com/q/ae?s=" + symbol;
var restquery = y.rest(url);
var rawresult = restquery.accept("text/html").get().response;
var aequery = y.xpath(rawresult, "//table[#class='yfnc_tableout1']/tr[count(td)=0]/parent::*|" + "//table[#class='yfnc_tableout1']/tr/td/table");
// Process Results
var aedata = < results symbol = {
symbol
} > < /results>; var i = 0; while(i < aequery.length()) { var table = aequery[i]; var thead = table.tr[0]; var tname = thead.th[0].strong.text().toString().replace(/ / g,
"");
var fname1 = thead.th[1].p.text().toString().replace(/\n.*/, "");
var fname2 = thead.th[2].p.text().toString().replace(/\n.*/, "");
var fname3 = thead.th[3].p.text().toString().replace(/\n.*/, "");
var fname4 = thead.th[4].p.text().toString().replace(/\n.*/, "");
fname1 = fname1.replace(/[\s\.]+/g, "").replace(/\&/, "");
fname2 = fname2.replace(/[\s\.]+/g, "").replace(/\&/, "");
fname3 = fname3.replace(/[\s\.]+/g, "").replace(/\&/, "");
fname4 = fname4.replace(/[\s\.]+/g, "").replace(/\&/, "");
var tblval = < {
tname
} > < /{tname}>; var j = 1; while(j < table.tr.length()) { var row = table.tr[j].td; var rname = row[0].p.text().toString().replace(/ [\s\.] + /g, ""); rname = rname.replace(/\ (.*\) / g,
"").replace(/\%/, "").replace(/^(\d)/, "_$1");
rname = rname.replace(/\//, "");
var rval1 = getelement(row[1]);
var rval2 = getelement(row[2]);
var rval3 = getelement(row[3]);
var rval4 = getelement(row[4]);
tblval.appendChild( < {
rname
} > < {
fname1
} > {
rval1
} < /{fname1}> <{fname2}>{rval2}</ {
fname2
} > < {
fname3
} > {
rval3
} < /{fname3}> <{fname4}>{rval4}</ {
fname4
} > < /{rname}>); j = j + 1; } aedata.appendChild(tblval); i = i + 1; }
// Return aedata strucuture
response.object = aedata;
Yes, the HTML structure for finance.yahoo.com has been changed somewhere in beginning of 2015, so YQL table implementation needs updating.
Please check the following GH pull requests which aims to fix the current outstanding issues:
GH-449: Update yahoo.finance.analystestimate.xml to new HTML structure
GH-457: Update yahoo.finance.analystestimate.xml
They're bit in overlap, so you may test them both (preferably check the first one).
Or you can check my fork of yql-tables (which consist loads of other fixes as well) where I've merged this PR into it, so find updated yahoo.finance.analystestimate.xml in here, the other one doesn't merge on top of the other one.

Invalid Property on Extended FIORI Application

We are implementing an extended My Quotations Fiori application. Basically we added a new field Sales Order to the UI. The field fetches data from the backend so we also extended our OData service. On the first view, we can successfully call the data. But whenever we navigate to the next view via clicking Edit button, we get this error
Property 'SalesOrder' is invalid. Choose "Refresh" to update pricing information.
Anyone has an idea on how to solve this?
Here is our custom code for S3 view controller. We used WEB IDE to create the extension btw. The second function is for the creation of the Sales Order whenever the quotation has no associated SO tied to it.
manageSalesOrderFields: function() {
alert("manageSalesOrderFields");
var salesOrderId = "";
// hide all fields
view.byId("salesOrderLabel").setVisible(false);
view.byId("salesOrderText").setVisible(false);
view.byId("triggerSalesOrderLabel").setVisible(false);
view.byId("triggerSalesOrderButton").setVisible(false);
$.getJSON("/sap/opu/odata/sap/zlord_my_quotation_srv/QuotationHeaderSet('" + quotationId + "')",
function(data) {
alert("enterHere");
salesOrderId = data.d.SalesOrder;
alert(salesOrderId);
if (salesOrderId !== "" ){
view.byId("salesOrderLabel").setVisible(true);
view.byId("salesOrderText").setVisible(true);
}else{
view.byId("triggerSalesOrderLabel").setVisible(true);
view.byId("triggerSalesOrderButton").setVisible(true);
view.byId("triggerSalesOrderButton").detachPress(sap.ui.controller("...").createSalesOrder);
view.byId("triggerSalesOrderButton").attachPress(sap.ui.controller("...").createSalesOrder);
}
});
},
createSalesOrder: function () {
var createSalesOrderDialog = new sap.m.Dialog("createSoDialog", {
title: "Create Sales Order",
icon: "sap-icon://sales-order",
content: [
new sap.ui.core.HTML({content:"<p style='margin:0;padding: 16px;'>Do want to create a sales order?</p>"})
],
buttons:[
new sap.m.Button({
text: "Yes",
press : function() {
var oModel = new sap.ui.model.odata.ODataModel('/sap/opu/odata/sap/zlord_my_quotation_srv/');
var oParameter = {
"QuotationID" : quotationId
};
oModel.callFunction('/CreateSalesOrder', 'GET', oParameter, 'null',
function (oData, oResponse) {
var responseMessage = JSON.stringify(oResponse.body);
var responseMessageStart = responseMessage.search('<d:Message>');
var responseMessageEnd = responseMessage.search('</d:Message>');
responseMessage = responseMessage.substring(responseMessageStart + 11, responseMessageEnd);
//show MessageToast
sap.m.MessageToast.show(responseMessage);
view.byId("triggerSalesOrderLabel").setVisible(false);
view.byId("triggerSalesOrderButton").setVisible(false);
console.log(responseMessage);
},
function (oError) {
sap.m.MessageToast.show('Error - see log');
console.log(oError);
}
);
createSalesOrderDialog.close();
createSalesOrderDialog.destroy();
}
}),
new sap.m.Button({
text: "No",
press : function() {
createSalesOrderDialog.close();
createSalesOrderDialog.destroy();
}
})
]
});
createSalesOrderDialog.open();
}
We didn't edit anything on the next view controller (CreateQuotations.view.controller.js) since it is not relevant for us to show the SO number on that view.
The error is because of this line:
salesOrderId = data.d.SalesOrder;
How to fix?
Step 1 : Check results first in network tab for the call:
/sap/opu/odata/sap/zlord_my_quotation_srv/QuotationHeaderSet('quotationIdId');
Sample:
Step 2: Check the results hierarchy . How?
console.log(data); //in success call
Step 3: Then restructure your statement to something like this
salesOrderId = data.d.results[0].SalesOrder;
Hope this helps!

Resources