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.
Related
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();
}
I'm trying to propogate a custom field value on the line of a sales order (SOLine) to the sales invoice (ARTran). I've looked at other examples but can't get the code to work...see below:
using PX.Objects.SO;
namespace PX.Objects.SO
{
public class SOInvoiceEntry_Extension:PXGraphExtension<SOInvoiceEntry>
{
#region Event Handlers
public delegate void InvoiceCreatedDelegate(ARInvoice invoice, SOOrder
source);
[PXOverride]
public void InvoiceCreated(ARInvoice invoice, SOOrder source,
InvoiceCreatedDelegate baseMethod)
{
baseMethod(invoice,source);
ARTran.RowInserted.AddHandler<ARTran>((cache, args) =>
{
var arTran = (ARTran)args.Row;
ARTranExt arTranExt = PXCache<ARTran>.GetExtension<ARTranExt>(arTran);
SOLineExt soLineExt = PXCache<SOLine>.GetExtension<SOLineExt>(soLine);
arTranExt.UsrContactID = soLineExt.UsrContactID;
});
}
#endregion
}
}
You want to put the handler on the graph which creates the ARTran, ARInvoiceEntry:
PXGraph.InstanceCreated.AddHandler<ARInvoiceEntry>((graph) =>
{
graph.RowInserting.AddHandler<ARTran>((sender, e) =>
{
}
}
The way you have it setup it will catch the event on the SOInvoiceEntry graph which is not the one inserting the ARTran lines, ARInvoiceEntry is the one insterting the lines.
InvoiceCreated is probably not the right place to put it though. Usually I put the event hook right before calling the CreateInvoice Action.
The sequence is:
With InstanceCreated you add the hook to any graph of generic type T that will be instantiated. In your case type is ARInvoiceEntry
Call CreateInvoice Action.
This Action will instantiate a ARInvoiceEntry graph and insert ARTran records in that ARInvoiceEntry graph context.
Your hook will be called in the right ARInvoiceEntry graph context so it will handle ARTran insertion.
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
How do I add an action and handler to the Process Shipments screen? We want to add an action to the Action combobox on screen SO503000, and then add a handler in code to process the new action. We want to do this without having to override the huge switch/case statement for Action in the SOShipmentEntry graph.
The PXAutomationMenu attribute pulls from Automation Steps all actions, which have appropriate processing screen set up as Mass Processing Screen:
To extend the list of actions available on the Process Shipments screen, please proceed as follows:
Declare custom action within a BLC extension and invoke AddMenuAction method during BLC initialization to add it as a drop-down item for the Actions button
To add custom action to the Process Shipments screen, add custom actions to appropriate automation step(s) and specify Mass Processing Screen ID. When user selects your custom action, Shipments from all Automations Steps, that contain a custom action, will be available for selection on the Process Shipments screen:
Two extensions (of the same 1st level) declared for the SOShipmentEntry BLC, as shown in the code snippet below, can be used to extend Actions drop-down with multiple customization projects (two customization packages that are independent of each other; either one or both may be published on a particular site. And both add an action to the Process Shipments screen):
To address this scenario, :
public class SOShipmentEntryExt1 : PXGraphExtension<SOShipmentEntry>
{
public PXAction<SOShipment> Test1;
[PXButton]
[PXUIField(DisplayName = "Test Action 1")]
protected void test1()
{
throw new PXException("Not implemented action: {0}", "Test Action 1");
}
public override void Initialize()
{
Base.action.AddMenuAction(Test1);
}
}
public class SOShipmentEntryExt2 : PXGraphExtension<SOShipmentEntry>
{
public PXAction<SOShipment> Test2;
[PXButton]
[PXUIField(DisplayName = "Test Action 2")]
protected void test2()
{
throw new PXException("Not implemented action: {0}", "Test Action 2");
}
public override void Initialize()
{
Base.action.AddMenuAction(Test2);
}
}