Given the following formflow setup...
return builder
.Field(new FieldReflector<CarValuationDialog>(nameof(Mileage))
.SetValidate(async (state, value) =>
{
var result = new ValidateResult { IsValid = true };
if (int.TryParse(value.ToString(), out int mileage))
{
state.Mileage = mileage;
result.IsValid = true;
}
else
{
result.Feedback = "That isn't valid number. Can you enter it again please?";
result.IsValid = false;
}
return result;
}))
.Field(new FieldReflector<CarValuationDialog>(nameof(HappyWithAnswersBuying))
.SetActive(carValuationDialog => carValuationDialog.ValuationOption == ValuationOptions.LookingToOwn)
.SetPrompt(CreateHappyWithAnswersBuyingPrompt())
.SetDefine(HappyWithAnswersDefinitionMethod))
.OnCompletion(GetValuationAndDisplaySummaryToUser)
.Build();
I want to try out custom validation on the mileage answer given by the user. The code in SetValidate executes, and given a valid integer, that number is assigned to state.Mileage.
However, when the prompt asking the user if they are happy with the answers is shown, the mileage is still set to 0..
Here is the code for the prompt..
private static PromptAttribute CreateHappyWithAnswersBuyingPrompt()
{
StringBuilder sb = new StringBuilder(100);
sb.Append("Are you happy with your answers? <br /><br />");
sb.Append("{&RegistrationNumber}: {RegistrationNumber} <br />");
sb.Append("{&Mileage}: {Mileage:N0} miles {||}");
return new PromptAttribute(sb.ToString())
{
ChoiceStyle = ChoiceStyleOptions.Buttons,
FieldCase = CaseNormalization.InitialUpper
};
}
Before I added the SetValidate code, the mileage was shown perfectly fine in the prompt. But since I'm now manually setting state.Mileage, it's not being persisted across for some reason.
Man, I seriously need to "check the plug" on my code...
Basically I had not set the Value property of the ValidateResult instance...
.SetValidate(async (state, value) =>
{
var result = new ValidateResult { IsValid = true, Value = value };
....
}
After doing that, the mileage value now appears in the prompt...
Related
Im a bit confused with whats happening bellow:
Can anyone explain me why is dataType keep saying StatisticData instead of SibsMonthly??
dataType is a enum? passad as ref param to this method and its initialized as null before the method call..
Here is the code:
string errorMessage = null;
Enums.Uploads.DataType? dataType = null;
if (ValidadeFile(file, ref errorMessage, ref dataType))
{
...
}
private bool ValidadeFile(IBrowserFile file, ref string errorMessage, ref Enums.Uploads.DataType? dataType)
{
List<string> acceptedFileTypes = new List<string>
{
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
};
var valid = true;
if (AcceptedFileNames != null && AcceptedFileNames.Length > 0)
{
var tmp = AcceptedFileNames.Where(x => new Regex(x.Regex).IsMatch(file.Name))
.OrderByDescending(x => x.DataType)
.Select(x => x.DataType)
.FirstOrDefault();
dataType = tmp;
if (dataType == null)
{
valid = false;
errorMessage = Constants.Uploads.Error.InvalidFileName;
}
}
...
}
The WASM debugger doesn't seem to like ref variables, it seems to look at their address, not their value:
Inspecting them at their call site or in the Debug window behaves as expected; this is a debugger isssue.
We have a plugin that uses the BookRequest & RescheduleRequest Methods to schedule Appointment & RecurringAppointmentMaster entities.
Recently I was tasked with implementing the ability to schedule multiple appts in a given timeslot.
So in researching this, I found some posts referring to resource capacity (in work hours) & setting the Effort field of the ActivityParty to 1.0 in the Appointment.
I thought, great this will be easy.
So I changed the plugin to store the effort:
activityParty = new Entity("activityparty");
activityParty["partyid"] = new EntityReference("systemuser", apptCaregiverId);
activityParty["effort"] = (Double)1.0;
But when I ran the code, the BookRequest returned this error that confused me: ErrorCode.DifferentEffort
I searched for ErrorCode.DifferentEffort, 2139095040, BookRequest, you name it found nothing useful.
What exactly does this error mean?
Why is it so difficult to schedule concurrent appointments in CRM?
This is what I learned the hard way, so maybe it will spare someone else some frustration.
I looked in the database & noticed that the activityparty entities associated with appointments all had the effort field set to 2139095040 and I wondered where that number was coming from? In a post unrelated to CRM, I found out the 2139095040 means 'positive infinity'.
I revisited the posts where they talk about setting the effort to 1.0 & realized that they were all referring to the ServiceAppointment entity (not Appointment) and then I finally stumbled on the list of error codes
Scheduling Error Codes
DifferentEffort = The required capacity of this service does not match the capacity of resource {resource name}.
Not exactly the truth, but whatever.
The real issue is the Appointment entity does not reference a Service, therefore the capacity of the non-existent service is ‘Positive Infinity’ (2139095040).
Setting the Effort equal to anything but 2139095040 throws this error.
It isn’t really checking the capacity of the resource here, it’s just saying that the non-existent service capacity must be = 2139095040
Anyway to get around this I removed the logic that sets the effort = 1.0 and when BookRequest or RescheduleRequest returns ErrorCode.ResourceBusy, I check the Capacity vs the # appts scheduled in that timeslot & if there is capacity left over, I force it to overbook by using Create or Update.
private Guid BookAppointment(Entity appointment, bool setState, out List<string> errors)
{
Guid apptId = Guid.Empty;
try
{
BookRequest request = new BookRequest
{
Target = appointment
};
BookResponse booked = (BookResponse)this.orgService.Execute(request);
apptId = ParseValidationResult(booked.ValidationResult, setState, appointment, true, out errors);
}
catch (Exception ex)
{
errors = new List<string> { ex.GetBaseException().Message };
}
return apptId;
}
private Guid RescheduleAppointment(Entity appointment, out List<string> errors)
{ // used to reschedule non-recurring appt or appt in recurrence
Guid apptId = Guid.Empty;
try
{
RescheduleRequest request = new RescheduleRequest
{
Target = appointment
};
RescheduleResponse rescheduled = (RescheduleResponse)this.orgService.Execute(request);
apptId = ParseValidationResult(rescheduled.ValidationResult, false, appointment, false, out errors);
}
catch (Exception ex)
{
errors = new List<string> { ex.GetBaseException().Message };
}
return apptId;
}
private Guid ParseValidationResult(ValidationResult result, bool setState, Entity appointment, Boolean addNew, out List<string> errors)
{
Guid apptId = result.ActivityId;
errors = new List<string>();
if (result.ValidationSuccess == true)
{
if (setState == true)
{
SetStateRequest state = new SetStateRequest();
state.State = new OptionSetValue(3); // Scheduled
state.Status = new OptionSetValue(5); // Busy
state.EntityMoniker = new EntityReference("appointment", apptId);
SetStateResponse stateSet = (SetStateResponse)this.orgService.Execute(state);
}
}
else
{
String error;
String errortxt;
Boolean overbookAppt = true;
foreach (var errorInfo in result.TraceInfo.ErrorInfoList)
{
bool unavailable = false;
if (errorInfo.ErrorCode == "ErrorCode.ResourceNonBusinessHours")
{
errortxt = "{0} is being scheduled outside work hours";
}
else if (errorInfo.ErrorCode == "ErrorCode.ResourceBusy")
{
errortxt = "{0} is unavailable at this time";
unavailable = true;
}
else
{
errortxt = "failed to schedule {0}, error code = " + errorInfo.ErrorCode;
}
Dictionary<Guid, String> providers;
Dictionary<Guid, String> resources;
DateTime start = DateTime.Now, end = DateTime.Now;
Guid[] resourceIds = errorInfo.ResourceList.Where(r => r.EntityName == "equipment").Select(r => r.Id).ToList().ToArray();
if (unavailable == true)
{
if (appointment.LogicalName == "recurringappointmentmaster")
{
start = (DateTime)appointment["starttime"];
end = (DateTime)appointment["endtime"];
}
else
{
start = (DateTime)appointment["scheduledstart"];
end = (DateTime)appointment["scheduledend"];
}
Dictionary<Guid, Boolean> availability = GetAvailabilityOfResources(resourceIds, start, end);
resourceIds = availability.Where(a => a.Value == false).Select(t => t.Key).ToArray(); // get ids of all unavailable resources
if (resourceIds.Count() == 0)
{ // all resources still have capacity left at this timeslot - overbook appt timeslot
overbookAppt = true;
} // otherwise at least some resources are booked up in this timeslot - return error
}
if (errortxt.Contains("{0}"))
{ // include resource name in error msg
if (resourceIds.Count() > 0)
{
LoadProviderAndResourceInfo(resourceIds, out providers, out resources);
foreach (var resource in providers)
{
error = String.Format(errortxt, resource.Value);
errors.Add(error);
}
foreach (var resource in resources)
{
error = String.Format(errortxt, resource.Value);
errors.Add(error);
}
}
}
else
{ // no place for name in msg - just store it
errors.Add(errortxt);
break;
}
}
if (overbookAppt == true && errors.Count() == 0)
{ // all resources still have capacity left at this timeslot & no other errors have been returned - create appt anyway
if (addNew)
{
appointment.Attributes.Remove("owner"); // Create message does not like when owner field is specified
apptId = this.orgService.Create(appointment);
if (setState == true)
{
SetStateRequest state = new SetStateRequest();
state.State = new OptionSetValue(3); // Scheduled
state.Status = new OptionSetValue(5); // Busy
state.EntityMoniker = new EntityReference("appointment", apptId);
SetStateResponse stateSet = (SetStateResponse)this.orgService.Execute(state);
}
}
else
{
this.orgService.Update(appointment);
}
}
}
return apptId;
}
private Dictionary<Guid, Boolean> GetAvailabilityOfResources(Guid[] resourceIds, DateTime start, DateTime end)
{
Dictionary<Guid, Boolean> availability = new Dictionary<Guid, Boolean>();
QueryMultipleSchedulesRequest scheduleRequest = new QueryMultipleSchedulesRequest();
scheduleRequest.ResourceIds = resourceIds;
scheduleRequest.Start = start;
scheduleRequest.End = end;
// TimeCode.Unavailable - gets appointments
// TimeCode.Filter - gets resource capacity
scheduleRequest.TimeCodes = new TimeCode[] { TimeCode.Unavailable, TimeCode.Filter };
QueryMultipleSchedulesResponse scheduleResponse = (QueryMultipleSchedulesResponse)this.orgService.Execute(scheduleRequest);
int index = 0;
TimeInfo[][] timeInfo = new TimeInfo[scheduleResponse.TimeInfos.Count()][];
foreach (var schedule in scheduleResponse.TimeInfos)
{
TimeInfo resourceCapacity = schedule.Where(s => s.SubCode == SubCode.ResourceCapacity).FirstOrDefault();
Int32 capacity = (resourceCapacity != null) ? (Int32)resourceCapacity.Effort : 1;
Int32 numAppts = schedule.Where(s => s.SubCode == SubCode.Appointment).Count();
// resource is available if capacity is more than # appts in timeslot
availability.Add(resourceIds[index++], (capacity > numAppts) ? true : false);
}
return availability;
}
This is really a better solution than setting the Effort = 1 anyway because now we don’t need a one-time on-demand workflow to update existing data.
I hope this helps save you some time if you ever need to do this.
I am trying to do a validation on a textbox that can allow the input of one or more phone number in a single textbox. What I am trying to do is to send an message to the phone numbers included in the textbox.
I have no problem when I enter just one set of number into the textbox and the message can be sent.
However, whenever I type two sets of digit into the same textbox, my validation error will appear.
I am using user controls and putting the user control in a listview.
Here are my codes:
private ObservableCollection<IFormControl> formFields;
internal ObservableCollection<IFormControl> FormFields
{
get
{
if (formFields == null)
{
formFields = new ObservableCollection<IFormControl>(new List<IFormControl>()
{
new TextFieldInputControlViewModel(){ColumnWidth = new GridLength(350) ,HeaderName = "Recipient's mobile number *" , IsMandatory = true, MatchingPattern = #"^[\+]?[1-9]{1,3}\s?[0-9]{6,11}$", Tag="phone", ContentHeight = 45, ErrorMessage = "Please enter recipient mobile number. "},
});
}
return formFields;
}
}
And here is the codes for the button click event:
private void OkButton_Click(object sender, RoutedEventArgs e)
{
MessageDialog clickMessage;
UICommand YesBtn;
int result = 0;
//Fetch Phone number
var phoneno = FormFields.FirstOrDefault(x => x.Tag?.ToLower() == "phone").ContentToStore;
string s = phoneno;
string[] numbers = s.Split(';');
foreach (string number in numbers)
{
int parsedValue;
if (int.TryParse(number, out parsedValue) && number.Length.Equals(8))
{
result++;
}
else
{ }
}
if (result.Equals(numbers.Count()))
{
try
{
for (int i = 0; i < numbers.Count(); i++)
{
Class.SMS sms = new Class.SMS();
sms.sendSMS(numbers[i], #"Hi, this is a message from Nanyang Polytechnic School of IT. The meeting venue is located at Block L." + Environment.NewLine + "Click below to view the map " + Environment.NewLine + location);
clickMessage = new MessageDialog("The SMS has been sent to the recipient.");
timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += timer_Tick;
timer.Start();
YesBtn = new UICommand("Ok", delegate (IUICommand command)
{
timer.Stop();
idleTimer.Stop();
var rootFrame = (Window.Current.Content as Frame);
rootFrame.Navigate(typeof(HomePage));
rootFrame.BackStack.Clear();
});
clickMessage.Commands.Add(YesBtn);
clickMessage.ShowAsync();
}
}
catch (Exception ex)
{ }
}
}
I am trying to separate the two numbers with ";" sign.... and I am wondering if that is the problem. Or maybe it is the matchingpattern that I have placed in.
The answer is quite simple, create a bool property in your TextFieldInputControlViewModel something like
public bool AcceptMultiple {get;set;}
and to keep things dynamic, create a char property as a separator like below:
public char Separator {get;set;}
Now, modify your new TextFieldInputControlViewModel() code statement by adding values to your new fields like below:
new TextFieldInputControlViewModel(){Separator = ';', AcceptMultiple = true, ColumnWidth = new GridLength(350) ,HeaderName = "Recipient's mobile number *" , IsMandatory = true, MatchingPattern = #"^[\+]?[1-9]{1,3}\s?[0-9]{6,11}$", Tag="phone", ContentHeight = 45, ErrorMessage = "Please enter recipient mobile number. "},
Once it's done, now in your checkValidation() function (or where you check the validation or pattern match) can be replaced with something like below:
if(AcceptMultiple)
{
if(Separator == null)
throw new ArgumentNullException("You have to provide a separator to accept multiple entries.");
string[] textItems = textField.Split(Separator);
if(textItems?.Length < 1)
{
ErrorMessage = "Please enter recipient mobile number." //assuming that this is your field for what message has to be shown.
IsError = true; //assuming this is your bool field that shows all the errors
return;
}
//do a quick check if the pattern matching is mandatory. if it's not, just return.
if(!IsMandatory)
return;
//your Matching Regex Pattern
Regex rgx = new Regex(MatchingPattern);
//loop through every item in the array to find the first entry that's invalid
foreach(var item in textItems)
{
//only check for an invalid input as the valid one's won't trigger any thing.
if(!rgx.IsMatch(item))
{
ErrorMessage = $"{item} is an invalid input";
IsError = true;
break; //this statement will prevent the loop from continuing.
}
}
}
And that'll do it.
I've taken a few variable names as an assumption as the information was missing in the question. I've mentioned it in the comments about them. Make sure you replace them.
My script is entering into an infinite loop and I have no idea why. I am running this on validate field and I am preventing a change to the field if another vendor bill exists with the same reference number, forcing the user to change the "Reference Number" to be unique. Here is my code:
function validateField(type, name) {
if (uniqueReferenceNum(type, name) === false) {
return false;
}
return true;
}
function uniqueReferenceNum(type, name) {
if (name !== 'tranid') {
return true;
}
var tranID = nlapiGetFieldValue('tranid');
var vendor = nlapiGetFieldValue('entity');
var vendorName = nlapiGetFieldText('entity');
var filters = new Array();
var columns = new Array();
filters[0] = new nlobjSearchFilter('entity', null, 'is', vendor);
filters[1] = new nlobjSearchFilter('tranid', null, 'is', tranID);
filters[2] = new nlobjSearchFilter('mainline', null, 'is', 'T');
columns[0] = new nlobjSearchColumn('internalid');
results = nlapiSearchRecord('vendorbill', null, filters, columns);
if (!results) {
return true;
}
alert("There is already a vendor bill with reference # " + tranID + " for " + vendorName + ". Please verify and change the reference number before continuing.");
return false;
}
For those still facing this issue, you can set the field in question - in this case, Reference Number - to a 'falsy' value such as an empty string. Only return false after checking that the field contains a 'truthy' value. Then display the alert or dialog to the user. This should break the validation loop.
I was trying to implement the jQgrid using MvcjQgrid and i got this exception.
System.NotSupportedException was unhandled by user code
Message=The method 'Skip' is only supported for sorted input in LINQ to Entities. The method 'OrderBy' must be called before the method 'Skip'.
Though OrdeyBy is used before Skip method why it is generating the exception? How can it be solved?
I encountered the exception in the controller:
public ActionResult GridDataBasic(GridSettings gridSettings)
{
var jobdescription = sm.GetJobDescription(gridSettings);
var totalJobDescription = sm.CountJobDescription(gridSettings);
var jsonData = new
{
total = totalJobDescription / gridSettings.PageSize + 1,
page = gridSettings.PageIndex,
records = totalJobDescription,
rows = (
from j in jobdescription
select new
{
id = j.JobDescriptionID,
cell = new[]
{
j.JobDescriptionID.ToString(),
j.JobTitle,
j.JobType.JobTypeName,
j.JobPriority.JobPriorityName,
j.JobType.Rate.ToString(),
j.CreationDate.ToShortDateString(),
j.JobDeadline.ToShortDateString(),
}
}).ToArray()
};
return Json(jsonData, JsonRequestBehavior.AllowGet);
}
GetJobDescription Method and CountJobDescription Method
public int CountJobDescription(GridSettings gridSettings)
{
var jobdescription = _dataContext.JobDescriptions.AsQueryable();
if (gridSettings.IsSearch)
{
jobdescription = gridSettings.Where.rules.Aggregate(jobdescription, FilterJobDescription);
}
return jobdescription.Count();
}
public IQueryable<JobDescription> GetJobDescription(GridSettings gridSettings)
{
var jobdescription = orderJobDescription(_dataContext.JobDescriptions.AsQueryable(), gridSettings.SortColumn, gridSettings.SortOrder);
if (gridSettings.IsSearch)
{
jobdescription = gridSettings.Where.rules.Aggregate(jobdescription, FilterJobDescription);
}
return jobdescription.Skip((gridSettings.PageIndex - 1) * gridSettings.PageSize).Take(gridSettings.PageSize);
}
And Finally FilterJobDescription and OrderJobDescription
private static IQueryable<JobDescription> FilterJobDescription(IQueryable<JobDescription> jobdescriptions, Rule rule)
{
if (rule.field == "JobDescriptionID")
{
int result;
if (!int.TryParse(rule.data, out result))
return jobdescriptions;
return jobdescriptions.Where(j => j.JobDescriptionID == Convert.ToInt32(rule.data));
}
// Similar Statements
return jobdescriptions;
}
private IQueryable<JobDescription> orderJobDescription(IQueryable<JobDescription> jobdescriptions, string sortColumn, string sortOrder)
{
if (sortColumn == "JobDescriptionID")
return (sortOrder == "desc") ? jobdescriptions.OrderByDescending(j => j.JobDescriptionID) : jobdescriptions.OrderBy(j => j.JobDescriptionID);
return jobdescriptions;
}
The exception means that you always need a sorted input if you apply Skip, also in the case that the user doesn't click on a column to sort by. I could imagine that no sort column is specified when you open the grid view for the first time before the user can even click on a column header. To catch this case I would suggest to define some default sorting that you want when no other sorting criterion is given, for example:
switch (sortColumn)
{
case "JobDescriptionID":
return (sortOrder == "desc")
? jobdescriptions.OrderByDescending(j => j.JobDescriptionID)
: jobdescriptions.OrderBy(j => j.JobDescriptionID);
case "JobDescriptionTitle":
return (sortOrder == "desc")
? jobdescriptions.OrderByDescending(j => j.JobDescriptionTitle)
: jobdescriptions.OrderBy(j => j.JobDescriptionTitle);
// etc.
default:
return jobdescriptions.OrderBy(j => j.JobDescriptionID);
}
Edit
About your follow-up problems according to your comment: You cannot use ToString() in a LINQ to Entities query. And the next problem would be that you cannot create a string array in a query. I would suggest to load the data from the DB with their native types and then convert afterwards to strings (and to the string array) in memory:
rows = (from j in jobdescription
select new
{
JobDescriptionID = j.JobDescriptionID,
JobTitle = j.JobTitle,
JobTypeName = j.JobType.JobTypeName,
JobPriorityName = j.JobPriority.JobPriorityName,
Rate = j.JobType.Rate,
CreationDate = j.CreationDate,
JobDeadline = j.JobDeadline
})
.AsEnumerable() // DB query runs here, the rest is in memory
.Select(a => new
{
id = a.JobDescriptionID,
cell = new[]
{
a.JobDescriptionID.ToString(),
a.JobTitle,
a.JobTypeName,
a.JobPriorityName,
a.Rate.ToString(),
a.CreationDate.ToShortDateString(),
a.JobDeadline.ToShortDateString()
}
})
.ToArray()
I had the same type of problem after sorting using some code from Adam Anderson that accepted a generic sort string in OrderBy.
After getting this excpetion, i did lots of research and found that very clever fix:
var query = SelectOrders(companyNo, sortExpression);
return Queryable.Skip(query, iStartRow).Take(iPageSize).ToList();
Hope that helps !
SP