Automatically calculating and adding Landedcost - acumatica

I need to add a LandedCost in PO Receipt screen (PO302000) based on a fix percentage (which I can include as a custom field in PO Preferences). It should be automatically added by the time PO Receipt is released. Which event should be the best approach to trigger and add LandedCost?
Is that when user uncheck OnHold checkbox?
Or, User clicks on Release button? If yes then can I extend release action?

The method that releases the POReceipts is static we cannot override it.
However, we can override the places where this static method is being called. It is called in two places: 1) on the release Action of POReceiptEntry(graph) and 2) on the constructor of the POReleaseReceipt(graph) that sets the process delegate.
1) On the POReceiptEntry, you can extend this graph to first do your Custom code and then call base release method.
public class POReceiptEntry_Extension:PXGraphExtension<POReceiptEntry>
{
public PXSetup<POSetup> posetup;
#region Event Handlers
public PXAction<POReceipt> release;
[PXUIField(DisplayName = Messages.Release, MapEnableRights = PXCacheRights.Update, MapViewRights = PXCacheRights.Update)]
[PXProcessButton]
public virtual void Release()
{
//retrieve value from Custom field added on PO Preferences screen
//POSetup setup = posetup.Current;
//POSetupExt setupExt = setup.GetExtension<POSetupExt>();
LandedCostTran landedCost = Base.landedCostTrans.Insert();
landedCost.LandedCostCodeID = "YOURLANDEDCOSTCODE";
landedCost.InvoiceNbr = "YOURINVOICENUMBER";
landedCost.CuryLCAmount = 2; //Formula here using setupExt.UsrFieldPercentange
Base.landedCostTrans.Update(landedCost);
Base.release.Press();
}
#endregion
}
2) On the POReleaseReceipt graph, since the process delegate is set on the constructor of this graph, you can extend this graph and override Initialize() method to set your custom process delegate.
Your custom process delegate will have your custom code and then call base method.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Text;
using PX.Common;
using PX.Data;
using PX.Objects.CS;
using PX.Objects.IN;
using PX.Objects.AP;
using PX.Objects.PO;
using PX.Objects.GL;
using PX.Objects.CM;
using PX.Objects;
namespace PX.Objects.PO
{
public class POReleaseReceipt_Extension:PXGraphExtension<POReleaseReceipt>
{
public override void Initialize()
{
//Gets Process Delegate
var processDelegate = (PXProcessingBase<POReceipt>.ProcessListDelegate)Base.Orders.GetProcessDelegate();
//Change the process delegate that was created by the framework by your custom one.
Base.Orders.SetProcessDelegate(delegate (List<POReceipt> orders) { POReceiptsProc(orders, processDelegate); });
}
public static void POReceiptsProc(List<POReceipt> orders, PXProcessingBase<POReceipt>.ProcessListDelegate processDelegate)
{
//Execute your custom code here
//create POReceiptEntry graph, Loop through the orders, Access your Custom field, Add LandedCost
PXTrace.WriteInformation("Start Process execution");
POReceiptEntry graph = PXGraph.CreateInstance<POReceiptEntry>();
........
//Call the base action
if (processDelegate != null)
processDelegate(orders);
}
}
}

Related

Custom Action - Selecting the PXCache to act upon

TL;DR - I am creating a time clock feature on the Employee Time Activities Screen (EP307000). How does one select/use the default view already built into a screen (or graph)?
I have two custom actions - Stop_Timer and Pause_Timer.
I could not figure out how to act upon the "Activity" view already built into the Graph (PX.Objects.EP.EmployeeActivitiesEntry).
To solve the problem, I wrote my own PXSelect statement (seen below).
But, by writing my own PXSelect statement, I broke the filter already present on the screen. Additionally, I seem to have lost the ability to use the "DependonGrid" property of the "PXDSCallbackCommand".
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using PX.Common;
using PX.Data.EP;
using PX.Objects.CR;
using PX.Objects.CS;
using PX.Objects.CT;
using PX.Objects.IN;
using PX.Objects.PM;
using PX.SM;
using PX.Data;
using PX.TM;
using OwnedFilter = PX.Objects.CR.OwnedFilter;
using PX.Api;
using PX.Objects;
namespace PX.Objects.EP
{
public class EmployeeActivitiesEntry_Extension : PXGraphExtension<EmployeeActivitiesEntry>
{
#region Select
public PXSelectJoin<EPActivityApprove,
InnerJoin<PMTask,
On<PMTask.taskID, Equal<EPActivityApprove.projectTaskID>>,
LeftJoin<PMTimeActivity,
On<PMTimeActivity.noteID, Equal<EPActivityApprove.origNoteID>>>>,
Where<EPActivityApprove.ownerID, Equal<Current<EmployeeActivitiesEntry.PMTimeActivityFilter.ownerID>>,
And<EPActivityApprove.trackTime, Equal<True>>>> Activity;
#endregion
#region Actions
public PXAction<PX.Objects.EP.EmployeeActivitiesEntry.PMTimeActivityFilter> Stop_Timer;
[PXButton]
[PXUIField(DisplayName = "Stop")]
protected void stop_Timer()
{
EPActivityApprove row = Activity.Current;
PMTimeActivityExt pMTimeActivityExt = PXCache<PMTimeActivity>.GetExtension<PMTimeActivityExt>(row);
throw new PXException("You pressed Stop Timer for the row containing Date = " + (DateTime)row.Date);
// All of my actions
Base.Caches[typeof(PMTimeActivity)].Update(pMTimeActivityExt);
Base.Caches[typeof(EPActivityApprove)].Update(row);
}
public PXAction<PX.Objects.EP.EmployeeActivitiesEntry.PMTimeActivityFilter> Pause_Timer;
[PXButton]
[PXUIField(DisplayName = "Pause/Play")]
protected void pause_Timer()
{
EPActivityApprove row = Activity.Current;
PMTimeActivityExt pMTimeActivityExt = PXCache<PMTimeActivity>.GetExtension<PMTimeActivityExt>(row);
throw new PXException("You pressed Pause Timer for the row containing Date = " + (DateTime)row.Date);
// All of my actions
Base.Caches[typeof(PMTimeActivity)].Update(pMTimeActivityExt);
Base.Caches[typeof(EPActivityApprove)].Update(row);
}
#endregion
}
}
see customization package stored on Github - for laughter at my poor coding skills
If you are happy with the view in the base graph, just refer to it as Base.Activity in your graph extension. Accessing views and actions work similarly to how you did Base.Caches at the bottom of your code.
The current Activity record => Base.Activity.Current
EPActivityApprove row = Base.Activity.Current;
EPActivityApproveExt rowExt = row.GetExtension<>EPActivityApproveExt>();
rowExt.MyField = "My Value";
Base.Activity.Cache.Update(rowExt);
If you need to press the save button at some point...
Base.Save.Press();

How to call a method when clicking button

Very unfamiliar with Acumatica, I am trying to make a button clickable that I configured on the Employee Time Card screen. I want it to call a method when clicked but I have been unsuccessful.
Here is my the screen. The button is labeled PUNCH CARD.
Made some changes:
When I click on the PUNCH CARD button I do see a POST back to the server: https://dev.domain.tld/db/(W(20))/pages/ep/ep305000.aspx?PopupPanel=Inline&HideScript=On&TimeCardCD=TC00000001
I think I should be seeing some exception response on the screen but I see nothing.
Here is the aspx code. I don't know what to put in the DependOnGrid attribute. I just copied what was on Yuri Zaletskyy's blog. Is this always set to this or is there something specific to the site / page I am working with? If so how do I determine what to put here?
<px:PXToolBarButton Text="Punch Card" Visible="True" DependOnGrid="grdJiraProjects">
<AutoCallBack Target="" Enabled="True" Command="punchCard">
<Behavior CommitChanges="True" /></AutoCallBack>
<PopupCommand>
<Behavior CommitChanges="True" />
</PopupCommand></px:PXToolBarButton>
C# Code:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using PX.Common;
using PX.Data;
using PX.Objects.CR;
using PX.Objects.CS;
using PX.Objects.IN;
using PX.Objects.CT;
using PX.Objects.PM;
using PX.Objects.GL;
using System.Diagnostics;
using System.Globalization;
using PX.SM;
using PX.TM;
using PX.Web.UI;
using Branch = PX.Objects.GL.Branch;
using System.Text;
using PX.Objects.GL.FinPeriods.TableDefinition;
using PX.Objects.GL.FinPeriods;
using PX.Objects;
using PX.Objects.EP;
namespace TimeTracking
{
public class TimeCardMaint_Extension : PXGraphExtension<PX.Objects.EP.TimeCardMaint>
{
public PXSelect<PMTimeActivity> PMTimeActivity;
public PXAction<EPTimeCard> PunchCard;
#region Event Handlers
protected void EPTimecardDetail_UsrPIXIClockIn_FieldUpdated(PXCache cache, PXFieldUpdatedEventArgs e, PXFieldUpdated InvokeBaseHandler)
{
if(InvokeBaseHandler != null)
InvokeBaseHandler(cache, e);
var row = (PMTimeActivity)e.Row;
var rows = row.GetExtension<PMTimeActivityExt>();
updateTotalHours(cache, rows, row);
}
protected void EPTimecardDetail_UsrPIXIClockOut_FieldUpdated(PXCache cache, PXFieldUpdatedEventArgs e, PXFieldUpdated InvokeBaseHandler)
{
if(InvokeBaseHandler != null)
InvokeBaseHandler(cache, e);
var row = (PMTimeActivity)e.Row;
var rows = row.GetExtension<PMTimeActivityExt>();
updateTotalHours(cache, rows, row);
}
#endregion
public void punchCard()
{
throw new PXException("You Clicked Punch Time Card on Action Bar");
}
public void updateTotalHours(PXCache cache, PMTimeActivityExt rows, PMTimeActivity row)
{
if (rows.UsrPIXIClockIn != null && rows.UsrPIXIClockOut != null)
{
DateTime dtClockIn = DateTime.Parse(rows.UsrPIXIClockIn.ToString());
DateTime dtClockOut = DateTime.Parse(rows.UsrPIXIClockOut.ToString());
double total = (dtClockOut - dtClockIn).TotalHours;
rows.UsrPIXITotalHours = (decimal) total;
}
}
}
}
One common catch that results in the button action event handler not being called is using the wrong DAC for the action generic type parameter.
Make sure the action generic type matches the graph primary DAC. In your case this would be EPTimeCard:
public PXAction<EPTimeCard> PunchCard;
Do you have a Graph Extension?
That's where you should put the action event handler for the button.
In your case TimeCardMaint is the graph of the employee time card screen:
using PX.Data;
using PX.Web.UI;
using System.Collections;
namespace PX.Objects.EP
{
public class TimeCardMaint_Extension : PXGraphExtension<TimeCardMaint>
{
public PXAction<EPTimeCard> PunchCard;
[PXButton]
[PXUIField(DisplayName = "Punch Card")]
public virtual IEnumerable punchCard(PXAdapter adapter)
{
// This is where you handle button click event
return adapter.Get();
}
}
}
There is something special about the button you added. It is located in a grid toolbar. It's often the case that you want grid toolbar actions to operate on the currently selected line. For that to work you need to declare the action in ASP file and set it's DependOnGrid property:
This is explained in more details here:
http://blog.zaletskyy.com/dependongrid-in-acumatica-or-how-to-get-currently-selected-record-in-grid
This link could help you too:
http://blog.zaletskyy.com/add-button-to-grid-in-acumatica
Once everything is setup you could then access the currently selected record in the grid from your action event handler like this:
[PXButton]
[PXUIField(DisplayName = "Punch Card")]
public virtual IEnumerable punchCard(PXAdapter adapter)
{
EPTimeCardDetail row = Base.Activities.Current as EPTimeCardDetail;
if (row != null)
{
// Presumably do some Punch Card action on selected row from Details tab Activities grid
}
return adapter.Get();
}

Create Button Under Actions To Redirect To Report In Acumatica CA304000

I am trying to add an option under Actions in Acumatica on the Transactions screen CA304000. See below what I am trying to achieve:
using System;
using System.Collections;
using System.Collections.Generic;
using PX.Data;
using PX.Objects.Common;
using PX.Objects.AP;
using PX.Objects.CM;
using PX.Objects.CS;
using PX.Objects.GL;
using PX.Objects.IN;
using PX.Objects.TX;
using PX.Objects.EP;
using PX.Objects.CR;
using Avalara.AvaTax.Adapter;
using Avalara.AvaTax.Adapter.TaxService;
using AvaAddress = Avalara.AvaTax.Adapter.AddressService;
using AvaMessage = Avalara.AvaTax.Adapter.Message;
using CRLocation = PX.Objects.CR.Standalone.Location;
using PX.Objects;
using PX.Objects.CA;
namespace PX.Objects.CA
{
public class CATranEntry_Extension:PXGraphExtension<CATranEntry>
{
#region Event Handlers
public override void Initialize()
{
Base.action.AddMenuAction(ShowURL);
}
public PXAction<CAAdj> ShowURL;
[PXUIField(DisplayName = "Phieu Thu")]
[PXButton]
protected virtual void showURL()
{
CAAdj doc = Base.Document.Current;
if (doc.RefNbr != null)
{
throw new PXReportRequiredException(doc, "TNCA6401", null);
}
}
#endregion
}
}
This is however telling me that there is ('PX.Objects.CA.CATranEntry' does not contain a definition for 'Document' and no extension method 'Document' accepting a first argument of type 'PX.Objects.CA.CATranEntry' could be found) twice.
this TNCA6401 only have one paramenter Reference Number. Please be specific (Image is the best). I'm Noob. Thanks you.
You should be working with CAAdjRecords data view not Document.
CAAdj doc = Base.Document.Current;
should be CAAdj doc = Base.CAAdjRecords.Current
And Reference Nbr. is tied to AdjRefNbr field.
You could use Customization -> Inspect Element to identify screen is working with which Graph, Data View, DAC and DAC field.

Create Button Under Actions To Redirect To Report In Acumatica

I am trying to add an option under Actions in Acumatica on the Checks & Payment screen AP302000. See below what I am trying to achieve:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization;
using PX.Common;
using PX.Data;
using PX.Objects.CM;
using PX.Objects.CA;
using PX.Objects.CS;
using PX.Objects.GL;
using PX.Objects.CR;
using PX.Objects;
using PX.Objects.AP;
namespace PX.Objects.AP
{
public class APPaymentEntry_Extension:PXGraphExtension<APPaymentEntry>
{
#region Event Handlers
public PXAction<APPayment> ShowURL;
[PXUIField(DisplayName = "Print Remittance")]
[PXButton]
protected virtual void showURL()
{
APPayment doc = Document.Current;
if (doc.RefNbr != null) {
throw new PXReportRequiredException(doc.RefNbr, "AP991000", null);
}
}
#endregion
}
}
This is however telling me that there is no definition and no extension method for 'APPayment'. Can someone please walk me through how to achieve what I am trying to do?
Note that the report has only 1 parameter (RefNbr)
Thanks,
G
To Add a new Action in existing Actions Menu, you should override the Initialize() method and use AddMenuAction.
public class APPaymentEntry_Extension : PXGraphExtension<APPaymentEntry>
{
public override void Initialize()
{
Base.action.AddMenuAction(ShowURL);
}
public PXAction<APPayment> ShowURL;
[PXUIField(DisplayName = "Print Remittance")]
[PXButton]
protected virtual void showURL()
{
APPayment doc = Base.Document.Current;
if (doc.RefNbr != null)
{
throw new PXReportRequiredException(doc, "AP991000", null);
}
}
}
Document.Current should be accessed as Base.Document.Current in Extensions. You need to pass the DAC as first parameter in PXReportRequiredException if DAC has the appropriate parameter value. Alternatively, you can build parameters and pass it to PXReportRedirectException.
Dictionary<string, string> parameters = new Dictionary<string, string>();
parameters["ParameterName1"] = <Parameter Value>;
...
throw new PXReportRequiredException(parameters, <reportID>, "Report")

Adding additional filter field on process shipments

I have added a new custom field to SOShipment and SOShipmentFilter. I am trying to use it to filter the grid for Process Shipments and I am having problems with the code. I have done other customizations where I have extended code but I was able to call the baseHandler first and then execute my snippet when it returned. However when I override the delegate function, it just sets up a template with a return to baseMethod. When I put my code in to include the new filter field, I get compile errors for undefined fields/references. Do I need to copy all of the original code from the original delegate and include it in my override function? Below is my current override code:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using PX.Data;
using PX.Objects.CS;
using PX.Objects.IN;
using PX.Objects.AR;
using PX.Objects.CM;
using POReceipt = PX.Objects.PO.POReceipt;
using POReceiptLine = PX.Objects.PO.POReceiptLine;
using POLineType = PX.Objects.PO.POLineType;
using PX.Objects;
using PX.Objects.SO;
namespace PX.Objects.SO
{
public class SOInvoiceShipment_Extension:PXGraphExtension<SOInvoiceShipment>
{
#region Event Handlers
public delegate IEnumerable ordersDelegate();
[PXOverride]
public IEnumerable orders(ordersDelegate baseMethod)
{
if (filter.usrTruckNbr != null)
{
((PXSelectBase<SOShipment>)cmd).WhereAnd<Where<SOShipment.usrTruckNbr, GreaterEqual<Current<SOShipmentFilter.usrTruckNbr>>>>();
}
return baseMethod();
}
protected virtual void SOShipmentFilter_UsrTruckNbr_CacheAttached(PXCache cache)
{
}
#endregion
}
}
cmd is a location variable and you cannot access it from your extension. I see two ways you could achieve the result you want:
Copy the whole delegate function to your extension. This is not ideal because it will have to be verified and updated with every new version of Acumatica.
Filter data on the client side after it is returned from the original delegate but before it is displayed on screen. This is not as efficient as filtering on SQL but at least would remove the need to copy too much code to your extension. Here's a complete sample which will filter the list of shipments to return only documents where shipment quantity is even:
public class SOInvoiceShipment_Extension : PXGraphExtension<SOInvoiceShipment>
{
[PXFilterable]
public PXFilteredProcessing<SOShipment, SOShipmentFilter> Orders;
protected IEnumerable orders()
{
// Filter the list of shipments to return only documents where shipment quantity is even
foreach (SOShipment shipment in Base.Orders.Select())
{
if(shipment.ShipmentQty % 2 == 0)
{
yield return shipment;
}
}
}
public void SOShipmentFilter_RowSelected(PXCache sender, PXRowSelectedEventArgs e)
{
if (e.Row == null) return;
SOShipmentFilter filter = e.Row as SOShipmentFilter;
if (filter != null && !string.IsNullOrEmpty(filter.Action))
{
Dictionary<string, object> parameters = Base.Filter.Cache.ToDictionary(filter);
Orders.SetProcessTarget(null, null, null, filter.Action, parameters);
}
}
}
In all cases, you should not use PXOverride to override the view delegate; the customization tools unfortunately generate the wrong method signature when you try to override a delegate, and this will be improved. You should refer to the T300 training available here for more information on this type of customization (look for "Declaring or Altering a BLC Data View Delegate).

Resources