Acumatica - Set checkbox to true based on a different field - acumatica

I know that this is basic stuff and conditions, but I have an issue with a line that already exists in a service order. So we have Service Orders that will be imported into Acumatica and will already have a line. When in the Acumatica system someone will go to the Service Order screen (SD300100) and make the changes they wish after the import. In this case, they will change the Warranty Status, a custom field that we made, and it will change some values in the details and header. I have everything working, except for the first line that is brought in from the import on the detail line. So this order will come in with a line already inserted into the Labor tab. My issue is when we change the Warranty Status to warranty it should check another custom field's box called Warranty down in the detail line. I have this working for any newly inserted line but I can't get it with the already existing line. I have tried RowUpdated, RowUpdating, RowInserted, RowInserting on both the Header and Labor line data views. As well as the FieldUpdated, FieldUpdating and Selecting, on the header warranty selector and the Warranty checkbox in the details under the labor tab.
Here is my code:
public PXSelect<FSSODet, Where<FSSODet.sOID, Equal<Current<FSSODet.sOID>>>> FSSODets;
protected void FSServiceOrder_Usrwarrstat_FieldUpdated(PXCache cache, PXFieldUpdatedEventArgs e)
{
var row = (FSServiceOrder)e.Row;
if (row == null) return;
FSSODet line = FSSODets.Current;
if (line == null) return;
if (line != null){
FSServiceOrderExt rowExt = PXCache<FSServiceOrder>.GetExtension<FSServiceOrderExt>(row);
if(rowExt == null)
return;
if (rowExt.Usrwarrstat == null)
return;
if (rowExt.Usrwarrstat == "W"){
cache.SetValueExt<FSSODetExt.usrwarrantydetail>(line, true);
}
}
}
This was the last way I tried before going to here. If anyone has a different way, I can provide code for any of the methods metioned above, RowUpdated, RowUpdating, etc. Commit Changes is set to true on both fields.
In short, when the Usrwarrstat field is set to "W" in the Service Order header, I want the Usrwarrantydetail in the Labor Tab/Details to be set to true.
Update 1: So I used the 1st answer suggestion below and it did change the checkbox to checked, but it happens no matter what the status is. I only need it to be check if it is set to "W" or "P", the only the other option is "N" so I added a check to if it is set to "N" then it would be false. However, it is still saving it as true.
Here is the updated code:
public PXSelect<FSSODetService, Where<FSSODetService.sOID, Equal<Current<FSSODetService.sOID>>>> FSSODets;
protected void FSServiceOrder_Usrwarrstat_FieldUpdated(PXCache cache, PXFieldUpdatedEventArgs e)
{
var row = (FSServiceOrder)e.Row;
if (row == null) return;
FSSODetService line = FSSODets.Current;
if (line == null) return;
if (line != null){
FSServiceOrderExt rowExt = PXCache<FSServiceOrder>.GetExtension<FSServiceOrderExt>(row);
if(rowExt == null)
return;
if (rowExt.Usrwarrstat == null)
return;
if (rowExt.Usrwarrstat == "W" || rowExt.Usrwarrstat == "P"){
FSSODets.Cache.SetValueExt<FSSODetExt.usrwarrantydetail>(line, true);
}
if (rowExt.Usrwarrstat == "N"){
FSSODets.Cache.SetValueExt<FSSODetExt.usrwarrantydetail>(line, false);
}
}
}
}

Overall the logic looks good. There's a Cache object mismatch that could prevent SetValueExt from working:
// When this event handler is called by the framework
// PXCache cache object will be of type FSServiceOrder
// because the event is bound on FSServiceOrder DAC
protected void FSServiceOrder_Usrwarrstat_FieldUpdated(PXCache cache, PXFieldUpdatedEventArgs e)
When you call SetValueExt you should use a cache object of matching type:
// cache reference is of type FSServiceOrder but we want to modify FSSODetExt DAC field
cache.SetValueExt<FSSODetExt.usrwarrantydetail>(line, true);
// With extensions you have to use the base DAC cache
// you already declared a DataView on FSSODet so you can use it's cache reference
FSSODets.Cache.SetValueExt<FSSODetExt.usrwarrantydetail>(line, true);
Besides the DAC cache reference mismatch there's a possibility that you do successfully modify the field value but another event handler/mechanism modifies it again after you.
To check that you can declare a event handler on the target field, put a breakpoint in it and debug it using visual studio debug stack trace window. The stack trace will show which methods lead to the field modification.
protected void FSSODet_Usrwarrantydetail_FieldUpdated(PXCache cache, PXFieldUpdatedEventArgs e)
Also make sure Usrwarrstat field has the CommitChanges = True property set in the ASPX file or else modifying the field on screen won't execute the associated FieldUpdated event handler.

Related

How can I save value of user-defined in the database without clicking the save button

I have created a user-defined field (Last Activity Date) on the Opportunities (CR304000) screen which is populated from the last Start Date of the activity on the Activities tab of the Opportunities screen (CR304000) on RowSelected as shown on screenshot one. When an activity is added for an opportunity, the user is not required to click the save button on the form of the opportunity, therefore the user-defined field I have added does not save the value to the database. I would like the value of the user-defined field to be saved to the database whenever an activity is added to the opportunity and the value of the user-defined field changes without having to click the save button on the opportunity form as I need to use it on a generic Inquiry.
Screenshot 1
Code Snippet:
protected void CROpportunity_RowSelected(PXCache cache, PXRowSelectedEventArgs e)
{
var row = (PX.Objects.CR.CROpportunity)e.Row;
if (row == null)
{
return;
}
CRActivity cRActivity = PXSelect<CRActivity, Where<CRActivity.refNoteID, Equal<Required<PX.Objects.CR.CROpportunity.noteID>>>, OrderBy<Desc<CRActivity.startDate>>>.Select(Base, row.NoteID);
if (cRActivity == null)
{
return;
}
CROpportunityExt cROpportunityExt = row.GetExtension<CROpportunityExt>();
cROpportunityExt.UsrLastActivity = cRActivity.StartDate;
Base.Opportunity.SetValueExt<CROpportunityExt.usrLastActivity>(Base.Opportunity.Current, cROpportunityExt.UsrLastActivity);
Base.Save.SetPressed(true);
}
In my opinion, push the value to the opportunity instead from the CRActivityMaint graph in the CRActivity RowPersisted event instead of pulling it from the activity in CROpportunity RowSelected. It is not good practice to change values of DAC rows in the RowSelected event.
Your code would be as follows
In CRActivityMaintExt
In RowPersisted
if (!(e.Row?.EntityDescription?.ToLower().Contains("opportunity") ?? false)) return;
CROpportunity opportunity = SelectFrom<CROpportunity>.Where<CROpportunity.noteID.IsEqual<#P.asGuid>>.View.Readonly.Select(Base, e.Row.RefNoteID);
if (opportunity is null) return;
var oppExt = opportunity.GetExtension<YourExtension>();
oppExt.YourValue = e.Row.SomeValue;
Base.Caches<CROpportunity>().PersistUpdated(opportunity);

Requiring an SOContact field when contact is not being overridden

On the Sales Order screen Financial Settings tab, I need to make the Bill-To Contact Email field required. The email field needs to be required regardless of whether or not "Override Contact" is checked.
Typically you can make a field required by just setting PXDefault("", PersistingCheck = PXPersistingCheck.NullOrBlank). However, that is only working if "Override Contact" is checked.
If the contact is not overridden, then the email field is disabled, and it is ignoring the persisting check and not requiring the email field.
How can I require the email field even when the billing contact is not being overridden?
Here's my code right now. Again, this is working if the contact is overridden but has no effect when the contact is not overridden.
public class SOOrderEntry_Extension : PXGraphExtension<SOOrderEntry.CarrierRates, SOOrderEntry>
{
// Make Financial Settings Email required
[PXMergeAttributes(Method = MergeMethod.Append)]
[PXDefault("", PersistingCheck = PXPersistingCheck.NullOrBlank)]
public void SOBillingContact_Email_CacheAttached(PXCache sender) { }
}
Another Approach
I've also tried using an event handler to raise an exception. This approach is also not working.
public virtual void SOBillingContact_RowUpdated(PXCache cache, PXRowUpdatedEventArgs e)
{
SOBillingContact contact = (SOBillingContact)e.Row;
if (contact != null)
{
if (String.IsNullOrEmpty(contact.Email))
{
cache.RaiseExceptionHandling<SOBillingContact.email>(e.Row, ((SOBillingContact)e.Row).Email,
new PXSetPropertyException("Email is required", PXErrorLevel.Error));
}
}
}
This code is running but the exception appears to get suppressed and is never displayed.
Have you tried PXUIRequiredAttribute? It allows you to conditionally determine when the field is required. Otherwise, remove the PersistingCheck or add Required = true to the PXUIField attribute as well.
Example of PXUIRequiredAttribute:
[PXMergeAttributes(Method = MergeMethod.Append)]
[PXDefault]
[PXUIRequired(typeof(Where<Current<SOBillingContact.overrideContact>, Equal<True>>))]
public void SOBillingContact_Email_CacheAttached(PXCache sender) { }
Untested in your application, but this follows the format of what I use in my code.
Also, if you make it required, remember to open the field to enable entry so that you don't cause validation to prevent saving for a record that the user cannot make right. That "unlocking the field" could be required via either Automation Steps or UI Workflow or maybe in the RowSelected event.
I found a solution:
First, I did make the SOBillingContact.Email field required on the screen using Automation steps
But as explained in the original question, this only is effective when the billing contact is being overridden.
To get the field to be required when the billing contact is NOT being overridden (i.e. Override Contact = False), I added an event handler for SOOrder_RowPersisting. In the event handler, I both throw a row persisting exception and also raise exception handling on the billing contact email field.
Throwing the row persisting exception stops the record from being saved (which is what I want) and displays an error, but it also keeps the changes in the cache so that the user can just correct the error and save again without losing their changes. Raising the field exception attaches an exception to the field itself so that it's clear to the user what they need to fix. If you just raise the field exception without throwing a row persisting exception, it gets suppressed by subsequent events and never gets displayed.
public class SOOrderEntry_Extension : PXGraphExtension<SOOrderEntry>
{
protected virtual void SOOrder_RowPersisting(PXCache sender, PXRowPersistingEventArgs e)
{
SOOrder order = (SOOrder)e.Row;
if (order == null) { return; }
if (e.Operation == PXDBOperation.Insert || e.Operation == PXDBOperation.Update)
{
SOBillingContact billingContact = PXSelect<SOBillingContact,
Where<SOBillingContact.contactID, Equal<Required<SOOrder.billContactID>>>>.Select(Base, order.BillContactID);
PXCache billingContactCache = this.Base.Caches[typeof(SOBillingContact)];
if (billingContact.Email == null || billingContact.Email == "")
{
billingContactCache.RaiseExceptionHandling<SOBillingContact.email>(billingContact, billingContactCache.GetValueExt<SOBillingContact.email>(billingContact),
new PXSetPropertyException("Financial Email is required", PXErrorLevel.RowError));
throw new PXRowPersistingException(typeof(SOBillingContact.email).Name, null, "Financial email is required");
}
}
}
}

I need to conditionally set a field to be required

I'm customizing the Sales Order screen as follows:
I've added a custom boolean field added to the Order Types screen called 'Require Customer Order Number'.
I've added code to the BLC of the Sales Order screen where I want to conditionally make the CustomerOrderNumber field required, based on whether the 'Require Customer Order Number' field is checked or not.
I'm using the SOOrder_RowSelected event as follows:
protected virtual void SOOrder_RowSelected(PXCache sender, PXRowSelectedEventArgs e)
{
var soorder = (SOOrder)e.Row;
if (soorder == null) return;
string ordtype = soorder.OrderType;
var soot = (SOOrderType)PXSelect<SOOrderType,
Where<SOOrderType.orderType, Equal<Required<SOOrderType.orderType>>>>.Select(Base, ordtype);
if (soot != null)
{
var sootext = PXCache<SOOrderType>.GetExtension<SOOrderTypeExt>(soot);
if (sootext != null)
{
PXUIFieldAttribute.SetRequired<SOOrder.customerOrderNbr>(sender, sootext.UsrRequireCustOrdNbr == null ? false : (bool)sootext.UsrRequireCustOrdNbr);
}
}
}
This DOES put an asterisk on the CustomerOrderNumber field - but it doesn't spawn an error upon save if that field is empty.
Another issue is my PXSelect to get the record out of SOOrderType ALWAYS returns a null for the check box user field, even if it has a 'True' value in the database (which is why I put the ternary operator on the call). Even if I hard-code a 'true' value in the PXUIFieldAttribute.SetRequired call, it still doesn't spawn the error to prevent a save. The asterisk is there, but it doesn't work.
If I use a Cache_Attached event to add [PXDefault] it works perfectly - but this doesn't help me - I need it conditionally set.
Any ideas?
Required is used only for displaying the asterisk. PXDefault attribute is the one that makes the field mandatory based on PersistingCheck property value.
The issue is that PXUIFieldAttributes like PersistingCheck can only be set once at the time of graph creation. You can set it dynamically in the constructor/Initialize method but if you change the property after that it has no effects.
When I need a field to be mandatory based on a dynamic condition I remove the PXDefault attribute and validate the field manually in event handlers like RowPersisting:
public void PMTimeActivity_RowPersisting(PXCache sender, PXRowPersistingEventArgs e)
{
PMTimeActivity timeActivity = e.Row as PMTimeActivity;
if (timeActivity != null && PMTimeActivity.timeSpent == null)
{
PXUIFieldAttribute.SetError<PMTimeActivity.timeSpent>(sender, timeActivity, "'Time Spent' cannot be empty."));
}
}

How to enable a custom field on GL301000 when the Batch is Posted?

I need to add a custom checkbox named "Cleared" to the detail lines in Acumatica's Journal Transactions page (GL301000). Users must be able to check this box after the batch is Posted. When the user checks the box another custom field titled "Date Cleared" should record the date and time. Both values must be saved in the database. Acumatica disables the detail lines after the batch is posted. How can I do this?
I see an answer to a similar question here. The JournalEntry BLC seems to disable the detail lines using the ReadOnlyStateController rather than the CommonTypeStateController in the GetStateController method, so I believe this solution needs to be different. Also, the Journal Transactions page does not seem to be driven by automation steps like this similar question.
You are correct the Journal Transaction page is not driven by automation steps.
If you want to enabled your custom Fields on the GLTran(lines) of this screen, you need to override both GLTran_RowSelected and Batch_RowSelected on your JournalEntry graph extension. Also, you can reuse the IsBatchReadonly function used on the GetStateController method.
On the Batch_RowSelected event handler you will:
-Check if batch is readOnly using the isBatchReadOnly function from the GetStateController method.
-Then, set allowUpdate to true on the caches.
-Set readonly false to all GLTran Fields
-Then set readonly true to all GLTran fields but your Custom Fields.
Then on the GLTran_RowSelected event handler you will setEnabled your Custom Fields.
See sample below:
public class JournalEntry_Extension : PXGraphExtension<JournalEntry>
{
protected void GLTran_RowSelected(PXCache cache, PXRowSelectedEventArgs e, PXRowSelected del)
{
GLTran row = (GLTran)e.Row;
if (del != null)
del(cache, e);
if (row != null)
{
PXUIFieldAttribute.SetEnabled<GLTranExt.usrCustomField1>(cache, row, true);
PXUIFieldAttribute.SetEnabled<GLTranExt.usrCustomField2>(cache, row, true);
}
}
protected void Batch_RowSelected(PXCache cache, PXRowSelectedEventArgs e, PXRowSelected del)
{
Batch row = (Batch)e.Row;
if (del != null)
del(cache, e);
if (row != null)
{
if (IsBatchReadonly(row))
{
//Set Cache Allow Update
cache.AllowUpdate = true;
Base.GLTranModuleBatNbr.Cache.AllowUpdate = true;
//First Set ReadOnly false to all fields
PXUIFieldAttribute.SetReadOnly(Base.GLTranModuleBatNbr.Cache, null, false);
//Then Set ReadOnly true to all fields but your custom ones
PXUIFieldAttribute.SetReadOnly<GLTran.accountID>(Base.GLTranModuleBatNbr.Cache, null, true);
PXUIFieldAttribute.SetReadOnly<GLTran.subID>(Base.GLTranModuleBatNbr.Cache, null, true);
.......
}
}
}
//Function used on the GetStateController method
private bool IsBatchReadonly(Batch batch)
{
return (batch.Module != GL.BatchModule.GL && Base.BatchModule.Cache.GetStatus(batch) == PXEntryStatus.Inserted)
|| batch.Voided == true || batch.Released == true;
}
}

Business Account(BAccount) Fielddefaulting not working

I'm having an issue with this particular field defaulting method working. I've done many in the past with no issues. For some reason it's not assigning the ClassID on the Business Accounts page. I've gone through the debugger and stepped through cus.CustomerClassID which contained the correct value; additionally e.NewValue was assigned correctly in the debugger. But when the page actually opens the field remains blank. Is there a special case with field defaulting for BAccountMaint due to the generic inquiry?
protected void BAccount_ClassID_FieldDefaulting(PXCache cache, PXFieldDefaultingEventArgs e, PXFieldDefaulting InvokeBaseHandler)
{
if (InvokeBaseHandler != null)
InvokeBaseHandler(cache, e);
BAccount row = (BAccount)e.Row;
AR.Customer cus = PXSelect<AR.Customer, Where<AR.Customer.acctCD, Equal<Required<AR.Customer.acctCD>>>>.Select(Base, row.AcctCD);
if (row.AcctCD != null)
{
e.NewValue = cus.CustomerClassID;
e.Cancel = true;
}
}
Achieved a correct result by using rowselecting event instead of field defaulting.

Resources