Acumatica custom field SOLine transferred to ARTran - acumatica

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.

Related

Acumatica - Copy custom field contents from SO to IN

I have a similar type of issue as in
Acumatica refer custom field to another custom field on different screen
except that I am using custom source fields.
I created and added 2 fields to the SO Line to capture EDI data needed for invoicing. I created 2 new fields on the invoice line with the same name (different data class of course) on the SO Invoice form. Below is the code for 1 field on each of the forms:
SO301000 (Sales Orders):
[PXDBString(3)]
[PXUIField(DisplayName="Cust.Invoice Line Nbr.")]
[PXFormula(typeof(Selector<SOLineExt.usrCInvLine, ARTranExt.usrCInvLine>))]
SO303000 (Invoices):
[PXDBString(3)]
[PXUIField(DisplayName="Cust.Invoice Line Nbr.")]
It compiles but the data is not being copied to the invoice when created from shipment. I also added 1 of the fields to the shipment form for testing purposes but it does nit capture that value either.
Do I have this backwards?
After reviewing it into more details, you could override the Prepare Invoice method on SOOrderEntry graph (and also anywhere else is needed).
By overriding the PrepareInvoice Method of SOOrderEntry graph you could add a Handler(RowInserting) to SOInvoiceEntry graph to populate your new Custom field with the value on SOOrder.
See sample below where I'm doing similar action copying value from SOOrder(Header) to ARTran(details).
You can use this sample to achieve your goal: (In your case you could use ARInvoice instead of ARTran)
SOOrderEntry:
public class SOOrderEntry_Extension:PXGraphExtension<SOOrderEntry>
{
public PXAction<SOOrder> prepareInvoice;
[PXUIField(DisplayName = "Prepare Invoice", MapEnableRights = PXCacheRights.Select, MapViewRights = PXCacheRights.Select, Visible = false)]
[PXButton]
public virtual IEnumerable PrepareInvoice(PXAdapter adapter)
{
PXGraph.InstanceCreated.AddHandler<SOInvoiceEntry>((graph) =>
{
graph.RowInserting.AddHandler<ARTran>((sender, e) =>
{
//Custom logic goes here
var row = (ARTran)e.Row;
if (row == null)
return;
SOOrder order = Base.Document.Current;
if (order != null)
{
var tranExt = PXCache<ARTran>.GetExtension<ARTranExt>(row);
//Below to be change to your needs
//Here you can look for the SOLine of the SOOrder instead
var orderExt = PXCache<SOOrder>.GetExtension<SOOrderExt>(order);
if (orderExt != null && tranExt != null)
{
tranExt.UsrContactID = orderExt.UsrContactID;
}
//END
}
});
});
return Base.prepareInvoice.Press(adapter);
}
}
You could also find more information and instructions provided by my colleague on the stackoverflow link below:
Acumatica custom field SOLine transferred to ARTran

How to add condition for process buttons on Recognize Input VAT TX503500

I have to add a description field that is mandatory, so that the action of processing can be carried out, however I am a little confused, due to the fact that the field is in the filter area, to be copied later in the descriptions to be processed.
How can I customize the actions Process, ProcessAll?
I don't find these actions in Override Methods
thanks for helping me, I'm really new to this
sorry if my english is not so good
Basically, the Process/Process All actions are mapped to one method which is using the SetProcessDelegate method of the processing data view.
What you need to do is for first locating to the Graph(ProcessInputSVAT) of the Recognize Input VAT screen(TX503500). After opening the source code for that graph you can see that it's derived from the ProcessSVATBase class. And when you'll enter that class you'll see the mentioned SetProcessDelegate function called by Data View:
protected virtual void SVATTaxFilter_RowSelected(PXCache sender, PXRowSelectedEventArgs e)
{
SVATTaxFilter filter = (SVATTaxFilter)e.Row;
if (filter == null)
{
return;
}
this.SVATDocuments.SetProcessDelegate(delegate(List<SVATConversionHistExt> list)
{
ProcessSVATBase.ProcessPendingVATProc(list, filter);
});
}
So we've figured out which graph extension we should create. Now, it's necessary to override the RowSelected event of the SVATTaxFilter DAC in the extension graph.
public class ProcessSVATBaseExt : PXGraphExtension<ProcessSVATBase>
{
public virtual void SVATTaxFilter_RowSelected(PXCache sender, PXRowSelectedEventArgs e)
{
SVATTaxFilter filter = e.Row as SVATTaxFilter;
if (filter != null)
{
Base.SVATDocuments.SetProcessDelegate(delegate (List<SVATConversionHistExt> list)
{
// Here you can manage the list items and then call the base method
// ...
ProcessSVATBase.ProcessPendingVATProc(list, filter); // the base method
// Here you can manage the list items after the base method
// ...
});
}
}
}

Cancel a sales order

How can a particular sales order be cancelled via code? Perhaps I can call the ProcessOrders graph, loop through the select orders, and execute the Cancel Order method. Unfortunately I see no such method. The drop-down action is driven by the automation menu. I do not find a cancel order action in the standard sales order entry graph. So what is the best way to accomplish the goal?
Via code, I can manually set the cancelled flag and status. This seems to work, but I'm not sure that is recommended. Seems like I'm skipping something, and the automation menu should be the way.
I'm copying the answer from this Acumatica blog post since it does exactly what you need, call the 'Cancel Order' automation step from code: Running Automation Step from Code
To call automation step you have to:
Define a new custom PXView that will return record that we want to
process
Create an adapter, that will provide data for button handler. Adapter
will get data from custom PXview.
Create a separate instance of graph, that will handle action.
Code:
public class SOOrderEntry_Extension:PXGraphExtension<SOOrderEntry>
{
//Lets define additional button than will call automation button.
public PXAction<SOOrder> ButtonExample;
[PXButton()]
[PXUIField(DisplayName = "Button Example")]
public virtual IEnumerable buttonExample(PXAdapter adapter)
{
SOOrder order = Base.Document.Current;
//creating a graph that will process Internal command
SOOrderEntry graph = PXGraph.CreateInstance<SOOrderEntry>();
graph.Document.Current = graph.Document.Search<SOOrder.orderNbr>(order.OrderNbr, order.OrderType);
//Searching for correct button from that is defined in Automatin steps.
//All sub menues are adden under action button, so we can get them and iterate.
foreach (var action in (graph.action.GetState(null) as PXButtonState).Menus)
{
if (action.Command == "Cancel Order")
{
//Constructing dummy view that will always return only one record.
adapter = new PXAdapter(new DummyView(graph, graph.Document.View.BqlSelect, new List<object> { order }));
//defining a button command
adapter.Menu = action.Command;
//running button
return graph.action.Press(adapter);
}
}
return adapter.Get();
}
//Defining a dummy view that is inherited from PXView
internal class DummyView : PXView
{
//Storing list of records
List<object> _Records;
internal DummyView(PXGraph graph, BqlCommand command, List<object> records)
: base(graph, true, command)
{
_Records = records;
}
//Everytime when system calls select for the view, retun saved records.
public override List<object> Select(object[] currents, object[] parameters, object[] searches, string[] sortcolumns, bool[] descendings, PXFilterRow[] filters, ref int startRow, int maximumRows, ref int totalRows)
{
return _Records;
}
}
}
I would think it's preferable to call the Automation Step instead of manually setting the Cancelled field because if a user changes the Automation Step you would pick up the changes by calling it.
Note that when possible you should always use existing Graph when you need to manually handle data because this will trigger validations.
If you were to manually change the Cancelled field using SOOrderEntry instead of calling the automation steps, the following validation in SOOrderEntry would still apply:
protected virtual void SOOrder_Cancelled_FieldVerifying(PXCache sender, PXFieldVerifyingEventArgs e)
{
SOOrder row = (SOOrder) e.Row;
PXResultset<CCProcTran> trans = PXSelect<CCProcTran, Where<CCProcTran.origRefNbr, Equal<Current<SOOrder.orderNbr>>,
And<CCProcTran.origDocType, Equal<Current<SOOrder.orderType>>,
And<CCProcTran.refNbr, IsNull,
And<CCProcTran.docType, IsNull>>>>>
.Select(this);
CCProcTranHelper.UpdateCCPaymentState(row, trans);
if (row != null && (row.IsCCAuthorized == true || row.IsCCCaptured == true))
{
bool authIsValid = true;
if (row.IsCCAuthorized == true)
{
if (row.CCAuthTranNbr != null)
{
CCProcTran authTran = PXSelect<CCProcTran, Where<CCProcTran.tranNbr, Equal<Required<CCProcTran.tranNbr>>>>.Select(this, row.CCAuthTranNbr);
if (String.IsNullOrEmpty(authTran.DocType) == false && String.IsNullOrEmpty(authTran.RefNbr) == false)
{
authIsValid = false;
}
}
else
{
CCProcTran authTran = this.ccAuthTrans.Select(); //Double-checking for valid auth tran
if (authTran == null)
authIsValid = false;
}
if (authIsValid && row.CCAuthExpirationDate.HasValue)
{
authIsValid = row.CCAuthExpirationDate.Value > PXTimeZoneInfo.Now;
}
}
if (authIsValid)
{
sender.RaiseExceptionHandling<SOOrder.cCPaymentStateDescr>(row, row.CCPaymentStateDescr, new PXSetPropertyException(Messages.CannotCancelCCProcessed, PXErrorLevel.Error));
}
}
}

Acumatica Warning Message To Only Affect Shipment Screen SO302000

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.

Updating ALL SOLine items unit prices dynamically when a new SOLine is added

I have a stored procedure that's called by a PXAction. I know it's against Acumatica's best practices to use a stored procedure, but I have yet find an alternative solution for my goal. The stored procedure evaluates each line item and the price class it's associated with depending on the breakQuantity that determines the unit price. If multiple items belong to the same price class == or exceed the break quantity the unit price is reduced.
What I started with was a row updating
protected virtual void SOLine_RowUpdating(PXCache sender, PXRowUpdatingEventArgs e)
{
SOLine row = (SOLine)e.Row;
formalizeOrderTotal(row);
}
then in my formalizeOrderTotal function it performed a foreach loop on SOLine in lines.Select() to add up order quantity. As a test i just tried adding up all order quantities and applying it to every line item. This only updated after refreshing which negates the purpose of moving the stored procedure to a c# function/Acumatica event handler.
If anyone has some recommendations a good approach to updating all line items in cache it would be greatly appreciated if you could provide some input.
Try using Base.Transactions.View.RequestRefresh(); which will ask the grid to refresh itself. In this example, I am setting each line quantity to the number of SOLines present in the grid.
using PX.Data;
namespace PX.Objects.SO
{
public class SOOrderEntry_Extension:PXGraphExtension<SOOrderEntry>
{
protected virtual void SOLine_RowUpdating(PXCache sender, PXRowUpdatingEventArgs e)
{
SOLine row = (SOLine)e.Row;
formalizeOrderTotal(row);
}
private void formalizeOrderTotal(SOLine row)
{
foreach (SOLine line in Base.Transactions.Select())
{
if(line.Qty == Base.Transactions.Select().Count)
{
continue;
}
line.Qty = Base.Transactions.Select().Count;
Base.Transactions.Update(line);
Base.Transactions.View.RequestRefresh();
}
}
}
}

Resources