Custom Action - Selecting the PXCache to act upon - acumatica

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();

Related

Acumatica : is it possible and how customize the grid in AR521000 screen?

First of all, sorry for my English !!!
I need to customize the grid (DunningLetterList) in the screen AR521000.
In this grid, we have the BAccountid. In the database, each BAccountId (customer) has one (and only one) salesperson checked by default (checkbox "Default" in the "Salespersons" tab in the CustomerMaint).
I need to add the name of this default salesperson in the DunningLetterList, for each customer that appears on the list.
Is it possible, knowing that it's a process screen ? And if it's possible, how can I do that ?
Thanks in advance !!
I am afraid there is no easy solution without affecting performance. That's because the rows from grid are not inserted in cache as usual, but generated inside the data view delegate and added with cache.Hold(row);
Therefore the only solution that I found is to add the sales person's name on RowSelected.
(!) Beware this is not recommended by Acumatica customization best practices (although you might find this kind of selects here and there). Just to let you know, the SQL generated query is SELECT TOP (1).
The customization of business logic:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using PX.Data;
using PX.Objects.CM;
using PX.Objects.CS;
using PX.Objects.GL;
using PX.SM;
using static PX.Objects.Common.Extensions.CollectionExtensions;
using PX.Objects;
using PX.Objects.AR;
using ARDunningLetterList = PX.Objects.AR.ARDunningLetterProcess.ARDunningLetterList;
namespace PX.Objects.AR
{
public class ARDunningLetterListSP : PXCacheExtension<ARDunningLetterList>
{
#region SalesPerson
public abstract class salesPerson : PX.Data.BQL.BqlString.Field<salesPerson> { }
[PXString(60)]
[PXUIField(DisplayName = "Sales Person")]
public virtual string SalesPerson { get; set; }
#endregion
}
public class ARDunningLetterProcess_Extension : PXGraphExtension<ARDunningLetterProcess>
{
public virtual void ARDunningLetterList_RowSelected(PXCache sender, PXRowSelectedEventArgs e)
{
ARDunningLetterList row = e.Row as ARDunningLetterList ;
if (row == null) return;
SalesPerson sp = PXSelectJoin<SalesPerson,
InnerJoin<CustSalesPeople, On<SalesPerson.salesPersonID, Equal<CustSalesPeople.salesPersonID>>>,
Where<CustSalesPeople.bAccountID, Equal<Required<CustSalesPeople.bAccountID>>, And<CustSalesPeople.isDefault, Equal<True>>>>.SelectSingleBound(this.Base, null, row.BAccountID);
var rowExt = row.GetExtension<ARDunningLetterListSP>();
rowExt.SalesPerson = sp?.Descr;
}
}
}
Then you add the custom field in the grid.
Note: I've tried other solutions as well (like overriding the data view (adding joined tables), the delegate, or using attributes to calculate the value, but I had no luck).
I knew it wouldn't be easy !!
Thank you for your answer and your researches !!
It works perfectly !!
Do you know why I can't filter or sort this column ? I added the "AllowFilter" and "AllowSort" properties, but it doesn't work.

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();
}

Automatically calculating and adding Landedcost

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);
}
}
}

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.

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