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
Related
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 have customized the GLTran DAC to have a custom field for Tax Zone and it gets populated along with Tax Category and Tax ID if the "Create Tax Transactions" check box is selected. I have also customized the "Release" to populate this Tax Zone over to the TaxTran table.
The Journal Transactions are created by a custom processing page that reads an XML file and create the Journal Transaction document and also releases it.
Everything works fine in my local instance and also on our sandbox instance.
In production, when I process the XML files using the Processing page, everything is fine. But when I create Automation Schedule, the Journal Transactions are created with Tax Zone populated and the document is released without any error. However the Tax Zone is not populated in the TaxTran table!
I don't have the same problem with Automation Schedule in my local instance or Sandbox. All these instances are of same version of Acumatica.
public class GLTranExt : PXCacheExtension<PX.Objects.GL.GLTran>
{
#region UsrTaxZoneID
[PXDBString(10)]
[PXSelector(typeof(Search<TaxZone.taxZoneID>),
typeof(TaxZone.taxZoneID),
typeof(TaxZone.descr))]
[PXUIField(DisplayName = "Tax Zone")]
public virtual string UsrTaxZoneID { get; set; }
public abstract class usrTaxZoneID : IBqlField { }
#endregion
}
public class PostGraph_Extension : PXGraphExtension<PostGraph>
{
public PXSelect<GLTran, Where<GLTran.batchNbr, Equal<Required<GLTran.batchNbr>>, And<GLTran.accountID, Equal<Required<GLTran.accountID>>, And<GLTran.subID, Equal<Required<GLTran.subID>>, And<GLTran.branchID, Equal<Required<GLTran.branchID>>>>>>> GLTransactions;
public delegate void ReleaseBatchProcDelegate(Batch b, bool unholdBatch = false);
[PXOverride]
public void ReleaseBatchProc(Batch b, bool unholdBatch, ReleaseBatchProcDelegate baseMethod)
{
baseMethod(b, unholdBatch = false);
foreach(TaxTran rec in Base.GL_GLTran_Taxes.Select(b.Module, b.BatchNbr))
{
GLTran glTran = GLTransactions.Select(rec.RefNbr, rec.AccountID, rec.SubID, rec.BranchID);
GLTranExt glTranExt = PXCache<GLTran>.GetExtension<GLTranExt>(glTran);
if (glTranExt != null)
rec.TaxZoneID = glTranExt.UsrTaxZoneID;
Base.GL_GLTran_Taxes.Update(rec);
Base.GL_GLTran_Taxes.Cache.Persist(PXDBOperation.Update);
}
}
}
I had the same problem, and in my case it was difference in permissions between account from which Automation Schedule was executed, and processing screen was executed. In order to fix it, I've added those lines:
using (new PXImpersonationContext(PX.Data.Update.PXInstanceHelper.ScopeUser))
{
using (new PXReadBranchRestrictedScope())
{
}
}
first using makes running your code from user name admin, and second using makes your code running with permissions access to necessary branches
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 would like to override the standard method of the RowSelected event on the Service Orders screen. Specifically, the DocDesc field gets populated when you select a row item for the Labor tab. It will set the TranDesc to the DocDesc and I would like to keep this from happening. I am using Acumatica 6.1 which means that the Service Management Module is not standard in Acumatica during this time. I would like the method that populates this field to not run when the labor line is populated, so the DocDesc field would remain null or blank, this way the user can input their own description.
You should be able to customize the ServiceOrderEntry graph like any other graph :
protected virtual void FSServiceOrder_RowSelected(PXCache sender, PXRowSelectedEventArgs e, PXRowSelected bs)
{
...
}
See https://help.acumatica.com/(W(3))/Main?ScreenId=ShowWiki&pageid=4a05d4c2-cd8b-4131-bf3b-d05861de3ae6
You could override the method if it is virtual, like this :
public delegate void PersistDelegate();
[PXOverride]
public void Persist(PersistDelegate baseMethod)
{
...
baseMethod();
...
}
See https://help.acumatica.com/(W(3))/Main?ScreenId=ShowWiki&pageid=635c830e-4617-4d5c-9fa5-035952311aa9
You could also modify the base customization, but since you are not the owner it could get difficult to maintain and track the changes.
I am trying to create a routine to import orders from our EDI service into Acumatica. I have created a skeleton action:
public PXAction<EDOrderReviewFilter> GetOrders;
[PXProcessButton()]
[PXUIField(DisplayName = "Get Orders")]
protected virtual void getOrders()
{
EDOrderReview graph = PXGraph.CreateInstance<EDOrderReview>();
graph.Filter.Current.ReviewType = "A";
throw new PXRedirectRequiredException(graph, false, "Review");
}
I can finish the code to retrieve the orders and insert the sales orders, but I cannot get this to schedule. The order review graph display would not be included in the automated retrieval. The schedule appears to only allow scheduling of Process All even though the documentation says it should be a picklist of the actions in the graph. Can anyone help? Is there a better way to schedule the order retrieval? The current thought is to check every 15 minutes and import all new orders.
=============New information============================================
I am having trouble now making the calling graph show the spinning timer while orders are being retrieved. In the code below the EDGetOrders.cs is the new processing page that simply retrieves the orders. This will eventually be hidden and scheduled. EDOrderReview.cs is the original graph that allows review and adjustment of imported orders where I would like to have a button that will initiate an order retrieve and show some feedback that the process is running and then show some indication that it is finished. Using the PressButton method processes the retrieve synchronously and then the screen refreshes via the last three lines. The LongOperation method starts the process asynchronously and immediately redraws the screen. Am I using the LongOperation correctly?
// EDGetOrders.cs Separate graph to simply retrieve the orders
// Action to retrieve orders
public PXAction<EDGetOrderFilter> GetOrders;
[PXProcessButton()]
[PXUIField(DisplayName = "")]
protected virtual void getOrders()
{
getEDIOrders();
}
// This function performs all the work and works fine
public void getEDIOrders()
{
...
}
// This function is called on Process All and works fine and shows the spinning timer
public void ProcessOrder(List<EDIGetOrder> list, string type)
{
SOOrderEntry soOrderGraph = PXGraph.CreateInstance<SOOrderEntry>();
bool errorOccured = false;
string statusText = "";
foreach (EDIGetOrder ediOrder in list)
{
PXProcessing<EDIGetOrder>.SetCurrentItem(ediOrder);
getOrders();
statusText = "Orders Retrieved";
}
if (errorOccured)
throw new PXOperationCompletedWithErrorException(statusText);
else
throw new PXOperationCompletedException(statusText);
}
//EDOrderReview.cs Original graph I want to call getOrders from and show the spinning timer
//Action to create button
public PXAction<EDOrderReviewFilter> GetOrders;
[PXProcessButton()]
[PXUIField(DisplayName = "Get Orders")]
protected virtual void getOrders()
{
EDGetOrders getOrders = PXGraph.CreateInstance<EDGetOrders>();
//getOrders.GetOrders.PressButton();
PXLongOperation.StartOperation(this, delegate () { goGetOrders(); });
//Redraw the screen with the new orders
EDOrderReview graph = PXGraph.CreateInstance<EDOrderReview>();
graph.Filter.Current.ReviewType = "A";
throw new PXRedirectRequiredException(graph, false, "Review");
}
public static void goGetOrders()
{
EDGetOrders getOrders = PXGraph.CreateInstance<EDGetOrders>();
getOrders.getEDIOrders();
}
Unfortunately, the current documentation doesn't match with the actual behavior of the Automation Schedules screen. In reality, the Action Name field always stays disabled and can only show the Process All option. Hopefully, this explains why it won't be possible to schedule order retrieval from your current processing page.
An alternative solution would be to create a stand-alone processing screen just to retrieve orders from external EDI service, which you can schedule to run the Process All action every 15 minutes. You can hide this new processing screen from users by placing it in the Hidden folder of the SiteMap.
For sure, you can still keep the Get Orders button on your current processing screen and, if you implement your method to retrieve orders from external EDI service as static, it should be possible to invoke the same method from both your current and new processing screens.
Update to answer the New Information section:
You should throw PXRedirectRequiredException to show EDOrderReview after the GetEDIOrders operation is over:
public PXAction<EDOrderReviewFilter> GetOrders;
[PXProcessButton()]
[PXUIField(DisplayName = "Get Orders")]
protected virtual void getOrders()
{
EDGetOrders getOrders = PXGraph.CreateInstance<EDGetOrders>();
//getOrders.GetOrders.PressButton();
PXLongOperation.StartOperation(this, delegate ()
{
goGetOrders();
//Redraw the screen with the new orders
EDOrderReview graph = PXGraph.CreateInstance<EDOrderReview>();
graph.Filter.Current.ReviewType = "A";
throw new PXRedirectRequiredException(graph, false, "Review");
});
}
public static void goGetOrders()
{
EDGetOrders getOrders = PXGraph.CreateInstance<EDGetOrders>();
getOrders.getEDIOrders();
}