How to return back ODataModel in SAPUI5 to its original state after using setDeferredGroups? - sapui5

I have a SAPUI5 application that uses OData V2.
In one part of the application for deleting of the items in a list I have to close change set after each call.
Then I use the following code:
sGroupId = "dmsch" + new Date().getTime();
oDataModel.setDeferredGroups([sGroupId]);
for (var i = 0; i < aSelectedContexts.length; i++) {
var sObjectPath = aSelectedContexts[i].getPath();
this._deleteObject(sObjectPath, sGroupId, fnAllRequestCompleted, fnAllRequestFailed);
}
oDataModel.submitChanges({
groupId: sGroupId
});
And in the _deleteObject function I set different changeSetId for each request, the b:
_deleteObject: function(sObjectPath, sGroupId, fnSuccessCallBackFunction, fnFailedCallBackFunction) {
var oDataModel = this.getModel();
var sChangeSetId = "cs" + (new Date().getTime() * (1 + Math.random()));
oDataModel.remove(sObjectPath, {
groupId: sGroupId,
changeSetId: sChangeSetId,
......
Now after a successful delete as soon as I create a new entry by using the createEntry function it tries to send the data of that entry to the server.
The question is how can I reset the effect of setDeferredGroups function.
Note: I need to use setDeferredGroups, and I am sure it is reason of sending newly created entries automatically to the server by each change. I need to set the setting of the ODataModel back to its original state.
Note2: Here is something regarding oData Version 4 that explain this automatic behavior after a failure.

The SAP docs here - I've tried to summarize below.
The default change groups are
{"*": {
groupId: "changes"
}
}
And the default deferred groups are
["changes"]
You can reset the data model change groups to default using
oModel.setChangeGroups({"*": {
groupId: "changes"
}
});
oModel.setDeferredGroups(["changes"]);
With this default configuration, all changes to all entity types will be collected in the changes group, and are deferred (not sent to the server automatically).
So oModel.setChangeGroups(...) is how change groups are defined, and oModel.setDeferredGroups is how each of those groups is determined to be deferred or not
The reason I mention the default change groups AND the default deferred groups, is because if not set properly, you may see unexpected behavior when using two way data binding.
For example: removing the default change group by calling oModel.setChangeGroups({}) will result in all changes to all entity types NOT getting collected into any change group, and thus not being deferred. You will see any changes made sent to the server automatically.
So lets say you have an entity type Employee and you want any changes made to this entity type to be collected in one group and be deferred:
var oChangeGroups = oModel.getChangeGroups();
oChangeGroups.Employee = {groupId: "employees"};
oModel.setChangeGroups(oChangeGroups);
var aDeferredGroups = oModel.getDeferredGroups();
aDeferredGroups.push("employees");
oModel.setDeferredGroups(aDeferredGroups);
Now you have two change groups, * with ID changes and Employee with ID employees. Any changes made to any Employee entities will be in the employees group, and all other changes will be in the changes group.
So now any create/delete/update of an employee can be submitted separately from any other changes to other entity types
oModel.createEntry("/EmployeeSet", {
groupId: "employees",
properties: {
name: "New Guy"
}
});
oModel.submitChanges({groupId: "employees"});
From this point, to go back to the default and get rid of the employees change group, you can use what I wrote above to reset everything back to default.

Related

Is it possible to add a role to a user with alanning:roles in meteor from an template event?

I am fairly new to Meteor and have been having real trouble with this issue.
I would like to have a select element which updates the users role (once logged in) depending on the option selected. I'm storing the value of the option as a variable when the select is changed and trying to take this value as the name of the role to add to the user.
When I run my app and change the select, the role seems to pop up for a second (viewed in Mongol) before disappearing again. I created a small test to display an alert of the role for the user, which shows up containing the name of the role but once you OK it, the role has disappeared. Am I missing something here?
Here is my template containing the select element...
<template name="select">
<select id="select">
<option value="working">Looking for work</option>
<option value="hiring">Hiring</option>
</select>
</template>
And here is the client side code for the change event
Template.select.events({
'change #select': function (event) {
//remove any current roles added to the user as it will be either
//one or the other
Roles.removeUsersFromRoles( Meteor.userId(), 'working', 'hiring' );
//add a role to the current user with the value from select box
var value = $(event.target).val();
Roles.addUsersToRoles( Meteor.user(), value );
//each of these alerts displays correctly depending on the select
//value
var test = Roles.userIsInRole( Meteor.user(), 'hiring' ); // true
if (test===true){
alert('in hiring role');
}
var test2 = Roles.userIsInRole( Meteor.user(), 'working' ); // true
if (test2===true){
alert('in working role');
}
// either working or hiring
alert(Roles.getRolesForUser(Meteor.userId()));
// alert displays count of 1 when you select 'hiring'
alert(Roles.getUsersInRole('hiring').count());
}
});
Any help would be much appreciated, have been searching through the documentation and online for several days to no avail. Many thanks :)
You try to add roles in your client. However, the client reflects only the data from the server's Roles collection.
You need therefore to change your code to a server side method, that
a) checks wether the current user is permitted to change roles (warning here, potential security threats when not checking permissions)
b) checks, wether the targeted user exists
c) sets the roles for the given userId
There is a good example in the documentation on how to do that. This is a slightly modified version of it:
Meteor.methods({
'updateRoles'({userId, roles, group}) {
check(userId, String);
check(roles, [String]);
check(group, String);
// a) check permission
if (!this.userId || !Meteor.users.findOne(this.userId) || !Roles.userIsInRole(this.userId, 'update-roles', 'lifted-users'))
throw new Meteor.Error('403', 'forbidden', 'you have no permission to change roles');
// b) check target user
if (!Meteor.users.findOne(userId))
throw new Meteor.Error('404', 'user not found');
// c) update user's roles
Roles.setUserRoles(userId, roles, group);
return true;
}
});
This method assumes, that there is a special role/group combination for users, that are allowed to change roles. This should be only a very few people, like admins.
Also note, that this method sets the user roles by using Roles.setUserRoles. If you want to extend the roles you need to use Roles.addUserToRoles.
You can then call this method from your client like every Meteor method:
Template.select.events({
'change #select': function (event) {
// get value from select box
var roles = [$(event.target).val()];
// TODO create a second select for the group
var group = 'defaultUsers'
var userId = Meteor.userId();
Meteor.call('updateRoles', { userId, roles, group }, (err, res) => {
// handle err / res
console.log(Roles.userIsInRole(userId, roles, group)); // should return true
});
}
});
Note, that Roles on the client is a collection which is immediately subscribed to. Changes are reflected reactively. If you do not see the changes immediately

Logic for tracking entity framework property value changes in MVC

I think I am missing something in my understanding of tracking property value changes in entity framework.
I have an application where i store service requests. Whenever a team value in changed in the service request record, I want to create a team history record in a related teamhistory entity.
I have created the app in MVC using the standard scaffolding for controllers and views.
In the (post)edit task in the controller, the standard logic generated has the following code
if (ModelState.IsValid)
{
db.Entry(serviceRequest).State = EntityState.Modified;
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(serviceRequest);
I have modified that to include the creating of the teamhistory record and an individualhistory record for individual assigned within team. The code for creating these related records work, BUT i want these records only created when the values on team or member(individual) change from what they were previously.
So far the conditions i have specified due not trigger this correctly because I havent gotten the condition right. Below is the current code:
//string teamorig = db.Entry(serviceRequest).Property(u => u.Team).OriginalValue.ToString();
//string teamcurr = db.Entry(serviceRequest).Property(u => u.Team).CurrentValue.ToString();
//if (teamorig != teamcurr)
var TeamIsModified = db.Entry(serviceRequest).Property(u => u.Team).IsModified;
if (TeamIsModified)
{
serviceRequest.TeamAssignmentHistories.Add(new TeamAssignmentHistory()
{
DateAssigned = DateTime.Now,
AssignedBy = User.Identity.Name,
ServiceRequest = serviceRequest.Id,
Team = serviceRequest.Team
});
}
//=========================================================================================
// if individual assigned has changed add individual history record========================
var IndividualIsModified = db.Entry(serviceRequest).Property(u => u.Member).IsModified;
if (IndividualIsModified)
{
serviceRequest.IndividualAssignmentHistories.Add(new IndividualAssignmentHistory()
{
DateAssigned = DateTime.Now,
AssignedBy = User.Identity.Name,
ServiceRequest = serviceRequest.Id,
AssignedTo = serviceRequest.Member.Value,
});
}
//===========================================================================================
The var teamismodified logic doesnt work. When I save the page without making any changes on it- the logic kicks off because in debugging it thinks the field has been modified.
When I comment out that code and uncomment the code above it for original and currentvalues- ie the teamorig and teamcurr logic, teamcurr and teamorig have the same values in debug, even when they have been forced into a change on the save in the MVC view page. Because they have the same values, the if condition is false so the team history record is not created.
The above code has been sandwiched in between
db.Entry(serviceRequest).State = EntityState.Modified;
and
await db.SaveChangesAsync();
statements.
What am I not understanding about entity framework tracking changes in mvc? Why does think its modified when i make not changes to team, and why are teamorig and teamcurr the same when I do make the changes?
Any advice would be welcome. Thanks

Mark an order as "Full Payment" on Sage 200

I am inserting orders on Sage 200 through an application using the client side, C# and APIs.
I would like to check the "Full payment" checkbox on the "Payment with order" tab.
Currently, I am setting the PaymentType property, which is not working.
order.PaymentType = Sage.Accounting.SOP.SOPOrderPaymentTypeEnum.EnumSOPOrderPaymentTypeFull;
order is an instance of Sage.Accounting.SOP.SOPOrder.
Do you know how I can check that property?
The following method should supply the required results.
private static void SetPaymentWithOrder(Sage.Accounting.SOP.SOPOrder sopOrder)
{
// Indicate that order has payment
sopOrder.PaymentWithOrder = true;
// This is full payment order
sopOrder.PaymentType = Sage.Accounting.SOP.SOPOrderPaymentTypeEnum.EnumSOPOrderPaymentTypeFull;
// Fetch the the Payment Methods. SOPPaymentMethods contructor accepts the boolean flag whether to fetch payment methods including card processing method or not.
Sage.Accounting.SOP.SOPPaymentMethods paymentMethodsCollection = new Sage.Accounting.SOP.SOPPaymentMethods(false);
// Set the first payment method of the collection to the order
sopOrder.PaymentMethod = paymentMethodsCollection.First;
}
dont know if you ever managed to figure this one out or not.
Not sure if you knew this, but you cannot modify the Sales Order on the view form, or at least shouldn't be trying to do so.
Using either of the Enter/Amend Sales Order forms will allow you to do so.
What is potentially happening, is that the properties that the controls are bound to are not updating the UI after your code has run.
You can simply force this to happen using the following
Fetching the underlying bound object
public Sage.Accounting.SOP.SOPOrderReturn SOPOrderReturn
{
get
{
//Loop over the boundobjects collection
//check if the bound object is of the type we want - e.g. SOPOrderReturn
//if correct type, return this object
Sage.Common.Collections.BoundObjectCollection boundObjects = this.form.BoundObjects;
if (boundObjects != null)
{
foreach (object boundObject in boundObjects)
{
if (boundObject is Sage.Accounting.SOP.SOPOrderReturn)
{
this._sopOrderReturn = boundObject as Sage.Accounting.SOP.SOPOrderReturn;
break;
}
}
}
return this._sopOrderReturn;
}
}
Fetch the correct underlying form type that the amendable form is, suspending the databinding,
perform your changes,
resuming the databinding
Sage.MMS.SOP.MaintainOrderForm maintainOrderForm = this.form.UnderlyingControl as Sage.MMS.SOP.MaintainOrderForm;
maintainOrderForm.BindingContext[this.SOPOrderReturn].SuspendBinding();
this.SOPOrderReturn.PaymentWithOrder = true;
this.SOPOrderReturn.PaymentType = Sage.Accounting.SOP.SOPOrderPaymentTypeEnum.EnumSOPOrderPaymentTypeFull;
maintainOrderForm.BindingContext[this.SOPOrderReturn].ResumeBinding();
should do the trick.

Breeze is not storing the right value (that is set in code) when saving during entityChanged event

I'm have a Breeze, Typescript, MVC 5.2, Knockout, Entity Framework webapp. I try to update a value of an User entity when the user clicks on a row in a grid (kogrid). The value is (should be) saved in the entityChanged eventhandler, but in Fiddler I see that the property value has not changed and the entityAspect.entityState is set to Modified (!) The originalValuesMap has the old TenantId and is the only value in the map.
I subscribe to the entity changed event like this:
this.EntityManager.entityChanged.subscribe((data: breeze.EntityChangedEventArgs) => {
if (data.entityAction == breeze.EntityAction.PropertyChange) {
return this.EntityManager.saveChanges(<breeze.Entity[]> new Array(data.entity))
.fail((error) => alert("Failed. " + error));}
});
The data arrives correctly a the eventhandler. A savechanges call is made, but the changed value (tenantId) has not changed.
The eventhandler for the rowclick is as follows:
ViewModel).OnRowClick = (tenantId: KnockoutObservable<System.IGuid>, viewModel: Imp.Scripts._TenantListViewModel) => {
entityManager.fetchEntityByKey("User", viewModel.Settings().CurrentUser().UserId(), false)
.then(entityKeyResult => {
(<Imp.Classes.UserBreeze>entityKeyResult.entity).CurrentTenantId(tenantId());
//entityManager.saveChanges(<breeze.Entity[]> new Array(entityKeyResult.entity));
})
.fail((error)=> alert("Error setting current tenant. " + error));});
When I disable the entityChanged subscription and enable the comment out line entityManager.saveChanges.... the entity is saved correctly. If I uncomment the line, but keep the subscription, it does not work.
How can I save the changed entity automatically after it changes?
EDIT:
Workaround is to disable the entityChanged eventhandler temporarily before changing the value CurrentTenantId on the current user, save the entity manually and re-subscribe to the entityChanged event.
But this solution smells.
Few suggestions:
Consider throttling the saves if propertychanged events are fired frequently. Knockout has a rate limiting extender for this purpose.
You may also want to consider using breeze save queuing plugin so you don't need to worry about overlapping save calls as much.
To troubleshoot the save issue, try adding the following code immediately before calling saveChanges:
if (data.entityAction === breeze.EntityAction.PropertyChange) {
var pcArgs = <breeze.PropertyChangedEventArgs>data.args;
console.log('Property Changed. PropertyName: ' + pcArgs.propertyName + '; Old Value: ' + (pcArgs.oldValue ? pcArgs.oldValue.toString() : 'null') + '; New Value: ' + (pcArgs.newValue ? pcArgs.newValue.toString() : 'null') + ';');
}

Updating MongoDB in Meteor Router Filter Methods

I am currently trying to log user page views in meteor app by storing the userId, Meteor.Router.page() and timestamp when a user clicks on other pages.
//userlog.js
Meteor.methods({
createLog: function(page){
var timeStamp = Meteor.user().lastActionTimestamp;
//Set variable to store validation if user is logging in
var hasLoggedIn = false;
//Checks if lastActionTimestamp of user is more than an hour ago
if(moment(new Date().getTime()).diff(moment(timeStamp), 'hours') >= 1){
hasLoggedIn = true;
}
console.log("this ran");
var log = {
submitted: new Date().getTime(),
userId: Meteor.userId(),
page: page,
login: hasLoggedIn
}
var logId = Userlogs.insert(log);
Meteor.users.update(Meteor.userId(), {$set: {lastActionTimestamp: log.submitted}});
return logId;
}
});
//router.js This method runs on a filter on every page
'checkLoginStatus': function(page) {
if(Meteor.userId()){
//Logs the page that the user has switched to
Meteor.call('createLog', page);
return page;
}else if(Meteor.loggingIn()) {
return 'loading';
}else {
return 'loginPage';
}
}
However this does not work and it ends up with a recursive creation of userlogs. I believe that this is due to the fact that i did a Collection.find in a router filter method. Does anyone have a work around for this issue?
When you're updating Meteor.users and setting lastActionTimestamp, Meteor.user will be updated and send the invalidation signal to all reactive contexts which depend on it. If Meteor.user is used in a filter, then that filter and all consecutive ones, including checkLoginStatus will rerun, causing a loop.
Best practices that I've found:
Avoid using reactive data sources as much as possible within filters.
Use Meteor.userId() where possible instead of Meteor.user()._id because the former will not trigger an invalidation when an attribute of the user object changes.
Order your filters so that they run with the most frequently updated reactive data source first. For example, if you have a trackPage filter that requires a user, let it run after another filter called requireUser so that you are certain you have a user before you track. Otherwise if you'd track first, check user second then when Meteor.logginIn changes from false to true, you'd track the page again.
This is the main reason we switched to meteor-mini-pages instead of Meteor-Router because it handles reactive data sources much easier. A filter can redirect, and it can stop() the router from running, etc.
Lastly, cmather and others are working on a new router which is a merger of mini-pages and Meteor.Router. It will be called Iron Router and I recommend using it once it's out!