I need to show some message to the user after Purchase Receipt is released, so I added my code in Released Field updated event and I tried PXOperationCompletedException method to show some message, but the problem is, Purchase Receipts is released and Inventory Receipts is also created but not released. Can someone please suggest me how to show a custom message (like a popup) after Purchase Receipt is released.
public class INReleaseProcessExt : PXGraphExtension<INReleaseProcess>
{
protected void INRegister_Released_FieldUpdated(PXCache cache, PXFieldUpdatedEventArgs e, PXFieldUpdated BaseEvent)
{
BaseEvent?.Invoke(cache, e);
INRegister row = e.Row as INRegister;
if (row == null)
return;
if (row.OrigModule == "PO")
throw new PXOperationCompletedException("Purchse Receipt is released");
}
}
Throwing an exception will abort the current operation as well as display a message.
To display a message without aborting the current operation use the Ask method.
Related
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 have tried multiple ways, but getting Another process error in the default version of Acumatica 19.106.0020
On top of it i have a customized code on both customer and contact screen, my requirement to clear the value of the custom field that is created in contact table when customer is deleting from the screen AR303000 i need to set null value of the custom field for the deleted contact from the customer.
i have tried by setting value on Customer_RowDeleting event but continuously getting Another process error, below is the screenshot error
Below is the code that i was tried
protected virtual void Customer_RowDeleting(PXCache sender, PXRowDeletingEventArgs e, PXRowDeleting BaseEvent)
{
BaseEvent?.Invoke(sender, e);
Customer rows = e.Row as Customer;
if (rows == null)
return;
if (Base.BAccount.Cache.GetStatus(Base.BAccount.Current) == PXEntryStatus.Deleted)
{
foreach (Contact BACT in PXSelectReadonly<Contact,
Where<Contact.bAccountID, Equal<Required<Contact.bAccountID>>,
And<Contact.contactType, NotEqual<ContactTypesAttribute.bAccountProperty>>>>.Select(Base, rows.BAccountID))
{
ContactMaint congraph = PXGraph.CreateInstance<ContactMaint>();
Contact CTData = PXSelectReadonly<Contact,
Where<Contact.contactID, Equal<Required<Contact.contactID>>>>.Select(Base, BACT.ContactID);
if (CTData != null)
{
congraph.Contact.Current = CTData;
if (congraph.Contact.Current != null)
{
congraph.Contact.SetValueExt<ContactExt.usrKWBAccountId>(congraph.Contact.Current, null);
congraph.Contact.Update(congraph.Contact.Current);
congraph.Save.Press();
}
}
}
}
}
Thanks in advance.
Hi Chris, please find the attached image here
I don't recommend to create graphs during RowDeleting event. If you have Acuminator installed, you will see a warning about creating graphs in event handlers.
Instead, call your custom code during the Persist method. Persist method is called during Delete operation. After the Base persist is finished, your custom code can perform it's work. Try something like this
public class CustomerMaint_Extension : PXGraphExtension<CustomerMaint>
{
public delegate void PersistDelegate();
[PXOverride]
public void Persist(PersistDelegate baseMethod)
{
Customer currentCustomer = Base.CurrentCustomer.Current; //get the customer record before it's deleted, i.e. Customer.bAccountID
baseMethod(); //let the base delete process happen first
if (Base.CurrentCustomer.Cache.GetStatus(currentCustomer) == PXEntryStatus.Deleted)
{
using (PXTransactionScope ts = new PXTransactionScope())
{
//here is where you add your code to delete other records
ts.Complete(); //be sure to complete the transaction scope
}
}
}
}
Also you might want to unpublish other customization packages, and see if the error continues without those packages. That is one way to determine the source of the error...by process of elimination.
I'm trying to display a warning every time the ShippedQty field is changed to a value < OrigOrderQty on the "Shipment - SO302000" screen, but I only want the code to be active for that specific screen/form.
I added the code below to extend the SOShipmentEntry graph, which accomplishes my original goal, but the issue is that now the code I added is also being used by the "Create Shipment" action in "Sales Orders - SO301000" screen/form.
Create Shipment Action Discussed
namespace PX.Objects.SO
{
public class SOShipmentEntry_Extension : PXGraphExtension<SOShipmentEntry>
{
#region Event Handlers
protected void SOShipLine_ShippedQty_FieldUpdated(PXCache cache,PXFieldUpdatedEventArgs e)
{
var myrow = (SOShipLine)e.Row;
if (myrow == null) return;
if (myrow.ShippedQty >= myrow.OrigOrderQty)
{
}
else
{
throw new PXSetPropertyException("The difference between the shipped-qty and the ordered-qty will be placed on a back-order", PXErrorLevel.Warning);
}
}
#endregion
}
}
While the warning allows the user to save changes to a shipment on the Shipment Screen/form SO302000 (Because the exception is set up as a warning and not an error), I get the following error when I create a shipment using the "Create Shipment" button on the "Sales Orders - SO301000" screen.
Warning works fine for form-mode
Warning becomes error when processed in background by action button
Any ideas to accomplish this? It is my understanding that if I want to make global changes to a field I must make them in the DAC, but if I want to make changes that only affect screens/forms where a graph is used, then I have to make those changes in the graph code itself. I'm guessing the "Create Shipment" action button in the Sales Orders screen is creating an instance of the graph where I added the code, so I'm wondering what are my options here.
Best regards,
-An Acumatica newbie
If you want your event logic to execute only when CreateShipment is invoked from the Shipment screen you can override the other calls to CreateShipment to dynamically remove your event handler.
The event that invokes CreateShipment action from the SalesOrderEntry graph is Action:
public PXAction<SOOrder> action;
[PXUIField(DisplayName = "Actions", MapEnableRights = PXCacheRights.Select)]
[PXButton]
protected virtual IEnumerable Action(PXAdapter adapter,
[PXInt]
[PXIntList(new int[] { 1, 2, 3, 4, 5 }, new string[] { "Create Shipment", "Apply Assignment Rules", "Create Invoice", "Post Invoice to IN", "Create Purchase Order" })]
int? actionID,
[PXDate]
DateTime? shipDate,
[PXSelector(typeof(INSite.siteCD))]
string siteCD,
[SOOperation.List]
string operation,
[PXString()]
string ActionName
)
In that method it creates a SOShipmentEntry graph to create the shipment. You can override Action and remove your handler from that graph instance:
SOShipmentEntry docgraph = PXGraph.CreateInstance<SOShipmentEntry>();
// >> Remove event handler
SOShipmentEntry_Extension docgraphExt = docgraph.GetExtension<SOShipmentEntry_Extension>();
docgraph.FieldUpdated.RemoveHandler<SOShipLine.shippedQuantity>(docgrapExt.SOShipLine_ShippedQty_FieldUpdated);
// << Remove event handler
docgraph.CreateShipment(order, SiteID, filter.ShipDate, adapter.MassProcess, operation, created);
Note that in order to reference SOShipLine_ShippedQty_FieldUpdated method from another graph you'll have to make it public:
public void SOShipLine_ShippedQty_FieldUpdated(PXCache cache,PXFieldUpdatedEventArgs e)
I have described how to do this in that answer too:
Updating custom field is ending into infinite loop
If you want your event logic to execute only when it is modified in the UI or by web service.
You can use the ExternalCall Boolean property of the PXFieldUpdatedEventArgs parameter.
This property value will be true only when the sender field is modified in the UI or by web service.
Usage example:
protected void SOShipLine_ShippedQty_FieldUpdated(PXCache cache,PXFieldUpdatedEventArgs e)
{
// If ShippedQty was updated in the UI or by a web service call
if (e.ExternalCall)
{
// The logic here won't be executed when CreateShipment is invoked
}
}
ExternalCall Property (PXFieldUpdatedEventArgs)
Gets the value specifying whether the new value of the current DAC field has been changed in the UI or through the Web Service API.
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.
We're using the Run Project Billing screen to create records in AR / Invoice and Memo.
In the Invoice & Memo screen, we need the process to populate the header Customer Ord. number, along with a user field that has been added to the grid section on the 'Document Details' tab. At the moment, the process is not doing this.
I'd like to intercept the processing action on the screen using a technique I'm familiar with, namely using an 'AddHandler':
[PXOverride]
protected virtual IEnumerable Items (PXAdapter adapter)
{
PXGraph.InstanceCreated.AddHandler<BillingProcess>((graph) =>
{
graph.RowInserting.AddHandler<BillingProcess.ProjectsList>((sender, e) =>
{
//Custom logic goes here
});
});
return Base.action.Press(adapter);
}
I see no Base.Actions that remotely resembles 'Bill' or 'Bill All'.
This is obviously not exactly the code I need, but I would think this is the general place to start.
After reviewing the source business logic, I don't see any 'Bill' or 'Bill All' Actions - or any 'Actions' at all (baffling). I see an IEnumerable method called 'items', so that's what I started with above.
Is this the correct way to go about this?
Update: 2/14/2017
Using the answer provided re: the overridden method InsertTransaction(...) I've tried to set our ARTran user field (which is required) using the following logic:
PMProject pmproj = PXSelect<PMProject, Where<PMProject.contractID, Equal<Required<PMProject.contractID>>>>.Select(Base, tran.ProjectID);
if (pmproj == null) return;
PMProjectExt pmprojext = PXCache<PMProject>.GetExtension<PMProjectExt>(pmproj);
if (pmprojext == null) return;
ARTranExt tranext = PXCache<ARTran>.GetExtension<ARTranExt>(tran);
if (tranext == null) return;
tranext.UsrContractID = pmprojext.UsrContractID;
Even though this sets the user field to the correct value, it still gives me an error that the required field is empty when the process finishes. My limited knowledge prevents me from understanding why.
On the Run Project Billing screen, captions of Process and Process All buttons were changed to Bill and Bill All respectively in BLC constructor.
Process delegate is set for Items data view within the BillingFilter_RowSelected handler:
public class BillingProcess : PXGraph<BillingProcess>
{
...
public BillingProcess()
{
Items.SetProcessCaption(PM.Messages.ProcBill);
Items.SetProcessAllCaption(PM.Messages.ProcBillAll);
}
...
protected virtual void BillingFilter_RowSelected(PXCache cache, PXRowSelectedEventArgs e)
{
BillingFilter filter = Filter.Current;
Items.SetProcessDelegate<PMBillEngine>(
delegate (PMBillEngine engine, ProjectsList item)
{
if (!engine.Bill(item.ProjectID, filter.InvoiceDate, filter.InvFinPeriodID))
{
throw new PXSetPropertyException(Warnings.NothingToBill, PXErrorLevel.RowWarning);
}
});
}
...
}
As code snippet above confirms, all records in the AR Invoice and Memos screen are created by instance of the PMBillEngine class. Below is code snippet showing how to override InsertNewInvoiceDocument and InsertTransaction methods within the PMBillEngine BLC extension:
public class PMBillEngineExt : PXGraphExtension<PMBillEngine>
{
public delegate ARInvoice InsertNewInvoiceDocumentDel(string finPeriod, string docType, Customer customer,
PMProject project, DateTime billingDate, string docDesc);
[PXOverride]
public ARInvoice InsertNewInvoiceDocument(string finPeriod, string docType, Customer customer, PMProject project,
DateTime billingDate, string docDesc, InsertNewInvoiceDocumentDel del)
{
var result = del(finPeriod, docType, customer, project, billingDate, docDesc);
// custom logic goes here
return result;
}
[PXOverride]
public void InsertTransaction(ARTran tran, string subCD, string note, Guid[] files)
{
// the system will automatically invoke base method prior to the customized one
// custom logic goes here
}
}
Run Project Billing process invokes InsertNewInvoiceDocument method to create new record on the AR Invoice and Memos screen and InsertTransaction method to add new invoice transaction.
One important thing to mention: overridden InsertNewInvoiceDocument and InsertTransaction methods will be invoked when a user launches Run Project Billing operation either from the processing Run Project Billing screen or from the data entry Projects screen.
For more information on how to override virtual BLC methods, see Help -> Customization -> Customizing Business Logic -> Graph -> To Override a Virtual Method available in every Acumatica ERP 6.1 website