saveChanges fails if key has a Date field (version 1.2.8) - breeze

We have an entity with three key fields, one of which is a date (don't ask - its a summary view with no other obvious key).
Breeze is throwing "This key is already attached" error when processing the response from the server after saving changes to the aforementioned entity.
The problem occurs in MergeEntity after saving changes. It seems that the initial lookup fails to find the entity on the client, so it tries to add it again resulting in the error.
Near the top of MergeEntity we find the following line...
var entityKey = EntityKey._fromRawEntity(node, entityType);
...which returns an entityKey._keyInGroup == "1535:::44:::2013-02-28T11:00:00.000Z". Note the third key field which looks like the JSON date string.
Later, when the new entity is (incorrectly) created its entityKey._keyInGroup == "1535:::44:::Fri Mar 01 2013 00:00:00 GMT+1300 (New Zealand Daylight Time)". Now the third field looks like a true javascript date.
The error finally occurs when we hit this line...
attachEntityCore(em, targetEntity, EntityState.Unchanged);
...get the "This key is already attached" error as the entity we've just saved was obviously already in the client cache all along.
Update: My hacks to Breeze to get this working...
1) I changed the _fromRawEntity function to check for dates and convert them properly so we get the same key values that are produced later for the real entity. (This code was copied from the updateEntity function so should behave identically).
ctor._fromRawEntity = function (rawEntity, entityType) {
var keyValues = entityType.keyProperties.map(function (p) {
var val = rawEntity[p.nameOnServer];
if (p.dataType.isDate && val) {
if (!__isDate(val)) {
val = DataType.parseDateFromServer(val);
}
}
return val;
});
return new EntityKey(entityType, keyValues);
};
2) Breeze now found the entity after saving changes but... I then got an error when Breeze tried to update the entities properties using the values returned from the server. I suspect this is due to an issue in the defaultPropertyInterceptor function which was checking to see if the property value had changed...
// exit if no change
if (newValue === oldValue) {
return;
}
This will always return false when comparing dates so I hacked this line to be:
if (newValue === oldValue || (dataType && dataType.isDate && newValue && oldValue && newValue.valueOf() === oldValue.valueOf())) {
return;
}
From my initial testing everything seems to be working but I'd greatly appreciate any thoughts from folks who are more familiar with breeze :)
Updated: Perhaps the last snippet would be more breeze-ish as...
// exit if no change
var comparable = dataType && getComparableFn(dataType);
if (newValue === oldValue || (comparable && comparable(newValue) === comparable(oldValue))) {
return;
}
...although that adds a bit more code considering it is running in every property set.

Edit: This was fixed in Breeze v1.3.0, available now.
Agreed, this is a bug! It will be fixed in the next release, out early next week. ( and we now have a test that involves a date as part of a primary key :)
and thanks for finding, analyzing and reporting it. The analysis really helped.

Related

Internal Error in Google Sheets Custom Function with Multiple Input & Output Values - Change to use Range Formula

I have a Google Sheets table with a custom function. I have made a sanitized and reduced version of it here:
Google Sheet with Custom Function (CF)
The more rows I have in one tab, the more often I get the error:
"Error Internal error while executing the custom function."
Doing some research helped me to come to the conclusion that this issue is probably because the custom function is not used as a range formula as it is suggested here:
Optimizing Custom Functions
The custom function looks something like this:
function fifo(datesA, assetQtysA, transVolumesA, minHoldTime) {
//console.log("Original values: " + createCM({datesA, assetQtysA, transVolumesA, minHoldTime}));
var dates = [];
dates = Array.isArray(datesA) ? datesA : [datesA];
var assetQtys = [];
assetQtys = Array.isArray(assetQtysA) ? assetQtysA : [assetQtysA];
var transVolumes = [];
transVolumes = Array.isArray(transVolumesA) ? transVolumesA : [transVolumesA];
if (transVolumes.length === 0 || assetQtys.length === 0 || dates.length === 0) {
throw new Error('The transaction volumes, the asset quantities and the dates must each have at least one element!');
}
if (transVolumes.length !== assetQtys.length || transVolumes.length !== dates.length) {
throw new Error('The total transaction values, the asset quantities and dates must have the same amount of elements!');
}
if (assetQtys[0] < 0) {
throw new Error('The first transaction needs be a BUY transaction.');
}
//Something happening here ;-)
return [return_1, return_2, return_3, return_4, return_5, return_6];
Sorry, I'm not comfortable sharing the entire source code. But I hope you are able to understand.
So I'm trying to apply the range strategy (from the link above) on my special case, but have failed so far.
I have tried to apply this to the code, but I can't find my way out of multiple errors. I guess I'm on the wrong path here.
Anyone know how to manage this?
At least I hope after applying the range strategy the internal error message will disappear.
Many thanks for your help.

Acumatica Purchase Receipt Return: Open PO Line Default to false

After clicking the "Return" action on a selected item from a completed Purchase Receipt, we're trying to get the "Open PO Line" value to default to false. Anyone know how customize this?
The field defaulting seems to be overwritten when we press the "Return" button. We've tried several different events in the grid but none of the seem to work.
The desired result is to default the "Open PO Line" to false after a return and once the return is released the Purchase Order line associated with the return should remain completed.
Research
The AllowOpen field on POReceiptLine is a PXBool. This means that the value must be populated via a PXDBScalar, PXFormula, etc. or via some business logic in the graph. To see what is happening, let's look at the DAC for POReceiptLine...
#region AllowOpen
public abstract class allowOpen : PX.Data.BQL.BqlBool.Field<allowOpen> { }
protected Boolean? _AllowOpen;
[PXBool()]
[PXUIField(DisplayName = "Open PO Line", Visibility = PXUIVisibility.Service, Visible = true)]
public virtual Boolean? AllowOpen
{
get
{
return this._AllowOpen;
}
set
{
this._AllowOpen = value;
}
}
#endregion
As you can see, there isn't any logic to this field in the DAC, so we need to turn to the graph to see how it is used. (Even if there were logic in the DAC, we would need to see if the graph does something. However, logic in the DAC might have been an easy override with CacheAttached - unfortunately, not in this case.)
Let's turn to POReceiptEntry where the return is handled. We find AllowComplete and AllowOpen being set in the POReceiptLine_RowSelected event, as we would expect since it must be populated on the graph side of code having no logic in the DAC.
if ((row.AllowComplete == null || row.AllowOpen == null) && fromPO)
{
POLineR source = PXSelect<POLineR,
Where<POLineR.orderType, Equal<Required<POLineR.orderType>>,
And<POLineR.orderNbr, Equal<Required<POLineR.orderNbr>>,
And<POLineR.lineNbr, Equal<Required<POLineR.lineNbr>>>>>>
.Select(this, row.POType, row.PONbr, row.POLineNbr);
// Acuminator disable once PX1047 RowChangesInEventHandlersForbiddenForArgs [Legacy, moved the exception here from PX.Objects.acuminator because the condition was changed]
row.AllowComplete = row.AllowOpen = (row.Released == true) ?
(row.ReceiptType != POReceiptType.POReturn ? source?.Completed == true : source?.Completed != true) :
(source?.AllowComplete ?? false);
The field is populated in the row.AllowComplete = row.AllowOpen = (row.Released == true) ?... section of code.
Subsequently, we see that the CopyFromOrigReceiptLine method sets this value to false on the "destLine" being created.
destLine.AllowOpen = false;
As that isn't "true" then we know this isn't our spot. Continuing on, we see in UpdatePOLineCompletedFlag that AllowComplete and AllowOpen are being set. This could be our spot (or one of them).
row.AllowComplete = row.AllowOpen = poLineCurrent.AllowComplete;
Side note: It is worth noting that this line appears twice in an if then else. In both cases it is going to be executed, therefore it would be better coding practice to place this identical statement AFTER the if then else since both the if and else conditions will execute this same statement regardless of the if.
This particular use appears to be pulling the value from the AllowComplete field of the PO Line being received. At this point, you should consider if you need to look upstream at the PO Line to see if the field there needs to be manipulated as well. I cannot answer that for you as your business case will drive the decision.
There also is a line in the Copy method that sets AllowComplete and AllowOpen.
aDest.AllowComplete = aDest.AllowOpen = (aSrc.CompletePOLine == CompletePOLineTypes.Quantity);
The Copy method is overloaded, and the other signature of the method sets the values to true.
aDest.AllowComplete = true;
aDest.AllowOpen = true;
Both of these cases may need customization as well, but I don't think it's the primary issue.
Next Steps
At this point, we see that either the field is being set in UpdatePOLineCompletedFlag or in methods that seem related to copying records. You will need to investigate further if the copy related methods warrant a change as well. However, I think the initial focus should be on the UpdatePOLineCompletedFlag method.
If we find the other points identified require customization, we likely will handle them all the same way... Override the base method/event, invoke the original method/event in our override, and then force the values to fit our business case. Careful testing will be needed since forcibly altering these values may create unforeseen negative ripples.
Something to try
We want to update (or create) a graph extension for POReceiptEntry to override the UpdatePOLineCompleteFlag method. This compiles, but it is completely untested on my part. We need to create a delegate and specify the PXOverride attribute. Then we want to execute the base method before we override the field(s) in question.
Note the extra code (commented out) as a reminder that you need to be careful of methods (typically events) updating our record in the cache and needing to be located so that we don't use a stale copy of the record. I don't think that's necessary in this case, but it seems to be somewhat obscure in code samples that I see. Of course, that is because I'm always looking at the code repository which rarely has graph extensions overriding event handlers!
#region CreateReceiptEntry Override
public delegate void UpdatePOLineCompleteFlagDelegate(POReceiptLine row, bool isDeleted, POLine aOriginLine);
[PXOverride]
public virtual void UpdatePOLineCompleteFlag(POReceiptLine row, bool isDeleted, POLine aOriginLine, UpdatePOLineCompleteFlagDelegate baseMethod)
{
//Execute original logic
baseMethod(row, isDeleted, aOriginLine);
/* If the base method has updated the cache, then we would need to locate the updated record in the cache to proceed
* This tends to be the case more often with event handlers, so it probably isn't needed in this case.
* This is just for reference as a training reminder
//If row has been updatd in the baseMethod, let's go get the updated cache values
POReceiptLine locateRow = Base.transactions.Locate(row);
if (locateRow != null) row = locateRow;
*/
//Override the fields to false - need to test to see if this creates any issues with breaking existing business logic
row.AllowComplete = row.AllowOpen = false;
}
#endregion
If this doesn't get you the specific answer you need, I hope it at least gives you some insight into how to hunt down "the spot" to change. I suspect you may need to update the POLine for a complete solution as hinted above. (See the event handler POReceiptLine_AllowOpen_FieldUpdated for the code that leads me to that conclusion.)
Good luck with your customization, and happy coding!

Dart compare two strings return false

Im new to dart and have a problem during building my Flutter application.
I have a firestore database as a backend and im getting data from there.
When i want to compare part of the data called status with the text 'CREATED', using == comparator, dart will return false.
Can someone explain why and how to check it properly?
rideObject is a Map
Update:
Here is the function that has the condition in it:
Widget _getPage() {
if (rideObject == null) {
return OrderRidePage(
address: address,
ridesReference: reference,
setRideReference: this._setRideReference);
} else {
print(rideObject['status']);
if (rideObject['status'] == "CREATED") {
return LoadingPage(
removeRideReference: this._removeRideReference,
rideReference: rideReference);
} else {
return RidePage(
address: address,
ridesReference: reference,
setRideReference: _setRideReference);
}
}
}
The print statement returns to output:
I/flutter (15469): CREATED
Here you can see the structure of the rideObject
Funnily enough, the rideObject["status"] is String type as shown in here in console:
rideObject["status"] is String
true
"CREATED" is String
true
rideObject["status"]
"CREATED"
rideObject["status"] == "CREATED"
false
The String you got from your server is probably encoded and contains special character which you can't see, try to compare the hex values of both of the strings, and then replace all the special characters from the String returned by the server.
Using this, you can see the actual non visible difference between the two strings:
var text1 = utf8.encode(hardcodedText).toString();
var text2 = utf8.encode(textFromServer).toString();
If both are really strings, you can use "compareTo" which will return 0 if both are equal.
if(str1.compareTo(str2)==0){
}
It is explained here:
https://www.tutorialspoint.com/dart_programming/dart_programming_string_compareto_method.htm
I don't have a particular solution to this, but I updated to latest Flutter version that came up today, moved the "CREATED" string into constant and resolved an unrelated warning for another part of the application, and it suddenly started to work.
The answer for this problem is in the documentation of flutter:
https://api.flutter.dev/flutter/dart-core/String/compareTo.html
you can do:
(var.compareTo('WORD') == 0)
are equivalent
.compareTo()
Returns a negative value if is ordered before, a positive value if is ordered after, or zero if and are equivalent.thisother
Building off #yonez's answer, the encoding may be different after a string has been passed through a server.
Instead of: String.fromCharCodes(data)
Try using: utf8.decode(data)

Where not null using Linq

I have a mySQL database with a Website field that is of type VarChar. Some of these fields will be null on the database.
I have an MVC application that I sending the database information to.
I am trying to set up a filter on the Index page so I can filter by certain columns. I am using the Request.QueryString to do this.
switch (Request.QueryString["FilterOptionSelect"])
{ case "CountyName":
if (!string.IsNullOrEmpty(filteredText))
{
filteredText = filteredText.ToUpper();
var modelFiltered = from n in model
where n.CountyName.ToUpper().Contains(filteredText)
select n;
return View(modelFiltered);
}
break;
case "Website":
if (!string.IsNullOrEmpty(filteredText))
{
filteredText = filteredText.ToUpper();
var modelFiltered = from n in model
where n.CountyWebsite.ToUpper().Contains(filteredText)
select n;
return View(modelFiltered);
}
break;
}
The only problem I have is on the Website case. It gives me a Object reference not set to an instance of an object. on the WHERE CLAUSE of the Website case. When I debug, my model is not null (it has 130+ items inside...some with website info and some without website info).
I have already tried using the Lambda method (which had the same problem). I have also tried using where n.CountyWebsite.ToUpper().Contains(filteredText) && n.CountyWebsite != null which did not work either.
You were on the right track with your second attempt, but you need to switch the order of those statements.
where n.CountyWebsite != null && n.CountyWebsite.Contains(filteredText)
Just like all the other && operators, you don't want to evaulate any websites that are null, so do that operation first.
Also, .Contains in EF automatically is case-insensitive, so you don't need the ToUpper().
Try this, I think this will solve.
!String.IsNullOrEmpty(n.CountyWebsite) && n.CountyWebsite.Contains(filteredText)

Cannot insert duplicate key

I am getting this error ...
Violation of PRIMARY KEY constraint 'PK_Members'. Cannot insert duplicate key in object 'dbo.Members'.
The statement has been terminated.
When I try to use the Membership and Role Providers in ASP.NET MVC. It happens when calling the GetUser method from inside the RoleProvider.
var member = System.Web.Security.Membership.GetUser(email) as Models.Member;
//var member = (
// from m in DataContext.Members
// where m.Email == email
// select m).Single();
var role = (
from r in DataContext.Roles
where r.Name == roleName
select r).Single();
member.Groups.Add(new Models.Group(role));
DataContext.SubmitChanges();
It looks like the problem is in the code
member.Groups.Add(new Models.Group(role));
Based on the error message returned by the sql, Read operation like GetUser won't throw this type of error.
I suspect it's because you are adding a group that exists already.
Maybe you should check for the existance of the role before trying to add it.
Hope this helps.
A good way to debug this is to use SQL profiler to determine what SQL code is being run against the database.
I would suspect you are trying to save a record somewhere that has the same primary key already in the database.
SQL Profiler = http://msdn.microsoft.com/en-us/library/ms181091.aspx
Are you sure you are not trying to enter a number into the PRIMARY KEY field that is already there? If it is auto_increment, just enter 0 and it will make the value of that field, the last number+1
Hope this helps :)
If the exception is an SqlException you might get its error number for duplicate records which is 2627. You might catch the exception and verify it and display and manage any error accordingly. I Hope this helps.
catch (SqlException ex)
{
if (ex.Number == 2627)
{
MessageBox.Show("This record exists: "+ex.Message, "Error");
}
else
{
MessageBox.Show(ex.Message, "Error")
}
}
I am a newbie at this but I am going to give this a try, sorry if it doesn't work for you.
I think that instead of using,
member.Groups.Add(new Models.Group(role));
You should use the following (if you are updating the database):
member.Groups.Entry(new Models.Group(role));
And if the above code doesn't work, try this (if you are adding to the database):
// First, search for the particular obj you want to insert
var checkModels = member.Groups.Find(new Models.Groups(roles));
// If the obj doesn't already exist, add it to the database
if(checkModels == null){
member.Groups.Add(new Models.Group(role));
}
// If the obj does exist already, then update it
else{
member.Groups.Entry(new Models.Group(role)).State = EntityState.Modified;
}

Resources