Adding additional filter field on process shipments - acumatica

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).

Related

How can I invoke File Synchonization Action via code (SM202530)

We’re using file synchronization to send payment batch files to an external site. File Synchronization only works on a per file basis so we have set up one file that our export scenario will populate.
Because we need every version of this file to be sent to the external site, I need to make sure that every revision of the file is synchronized.
My thought was to override the Export button on Batch Payments (AP305000) to call the Process All Files action, using the Export File operation, on the File Synchronization screen. The challenge is that I can’t see to find the name of the graph to instantiate to access that business object.
Here is where I’m at:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using PX.Api;
using PX.Data;
using PX.Data.WorkflowAPI;
using PX.Objects.AP;
using PX.Objects.AR;
using PX.Objects.CM;
using PX.Objects.Common.Extensions;
using PX.Objects.CR;
using PX.Objects.CS;
using PX.Objects.GL;
using PX.Objects.AP.MigrationMode;
using PX.Objects;
using PX.Objects.CA;
using PX.Objects.SM;
namespace PX.Objects.CA
{
public class CABatchEntry_Extension : PXGraphExtension<CABatchEntry>
{
[PXOverride]
public virtual IEnumerable Export(PXAdapter adapter)
{
Base.export.Press(adapter);
//Challenge is here
SynchronizationProcess docgraph = PXGraph.CreateInstance<SynchronizationProcess>();
foreach (var action in (docgraph.action.GetState(null) as PXButtonState).Menus)
{
if (action.Command == "Process All Files")
{
PXAdapter adapter2 = new PXAdapter(new DummyView(docgraph, docgraph.Document.View.BqlSelect, new List<object> { docgraph.Document.Current }));
adapter2.Menu = action.Command;
docgraph.action.PressButton(adapter2);
TimeSpan timespan;
Exception ex;
while (PXLongOperation.GetStatus(docgraph.UID, out timespan, out ex) == PXLongRunStatus.InProcess)
{ }
break;
}
}
return adapter.Get();
}
internal class DummyView : PXView
{
List<object> _Records;
internal DummyView(PXGraph graph, BqlCommand command, List<object> records)
: base(graph, true, command)
{
_Records = 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;
}
}
}
}
You can write your own PXLongOperation which will invoke the Processing Delegate the same way that page does, like below. Then you can use PXLongOperation.WaitCompletion(this.Base.UID) to wait till the process is completed and then do any other actions that you need to do.
public PXAction<SOOrder> RunSynchronization;
[PXButton(CommitChanges = true)]
[PXUIField(DisplayName = "Sync Files")]
protected IEnumerable runSynchronization(PXAdapter adapter)
{
PXLongOperation.StartOperation(this.Base, () =>
{
SynchronizationProcess docgraph = PXGraph.CreateInstance<SynchronizationProcess>();
docgraph.filter.SetValueExt<SynchronizationFilter.operation>(docgraph.filter.Current,"U"); //U - export D - import
var records = docgraph.SelectedFiles.Select().RowCast<UploadFileWithIDSelector>().ToList();
docgraph.SelectedFiles.GetProcessDelegate().DynamicInvoke(records);
});
return adapter.Get();
}

How to get DAC record from Note Table

Does anyone have a code snippet on how to go from RefNoteId => DAC when I dont know what dac type the note is attached to?
I have made it this far (row.RefNoteID is what I am starting from)
Note note = PXSelect<Note, Where<Note.noteID, Equal<Required<Note.noteID>>>>.Select(this, row.RefNoteID);
Type recordType = Type.GetType(note.EntityType);
PXCache recordCache = Caches[recordType];
How can I now do a PXSelect<recordType, Where<recodType.noteID, Equal<Required<recordType.noteID>>>>.Select(GRAPH) ? The recordType could be any DAC in the system that has a noteID.
Thanks
The below code works for me and it is based on the way Acumatica gets the record inside the PXRefNoteSelectorAttribute.PrimaryRow_RowPersisted.
The problem with this approach is that this will work for Header entities like SOOrder, INRegister, SOInvoice, SOShipment, and others. But for "detail" entities like SOLine, INTran, and others this approach will work only if that corresponding record has some Note related to Text/File. Acumatica is adding records corresponding to their NoteID into the Note table only if that detail records have some Note/Text. My best guess is that this is done in order to avoid over-spamming the Note table.
using PX.Data;
using PX.Objects.SO;
using System;
using System.Collections;
using System.Linq;
using System.Web.Compilation;
namespace SearchByNoteID
{
// Acuminator disable once PX1016 ExtensionDoesNotDeclareIsActiveMethod extension should be constantly active
public class SOOrderEntryExt : PXGraphExtension<SOOrderEntry>
{
public PXAction<SOOrder> searchByNoteID;
[PXUIField(DisplayName ="Search by Note ID")]
[PXButton(CommitChanges = true)]
public virtual IEnumerable SearchByNoteID(PXAdapter adapter)
{
var order = adapter.Get<SOOrder>().FirstOrDefault();
if(order!=null)
{
//
//...
//
Guid? noteID = GetNoteID();
object record = GetRecordByNoteID(noteID);
//
//... do whatever you want with the record
//
}
return adapter.Get();
}
protected object GetRecordByNoteID(Guid? noteID)
{
var type = GetEntityType(this.Base, noteID);
if(type==null) return null;
object entityRow = new EntityHelper(this.Base).GetEntityRow(type, noteID);
return entityRow;
}
protected Type GetEntityType(PXGraph graph, Guid? noteID)
{
if (noteID == null)
{
return null;
}
Note note = PXSelectBase<Note, PXSelect<Note, Where<Note.noteID, Equal<Required<Note.noteID>>>>.Config>.SelectWindowed(graph, 0, 1, new object[]
{
noteID
});
if (note == null || string.IsNullOrEmpty(note.EntityType))
{
return null;
}
return PXBuildManager.GetType(note.EntityType, false);
}
}
}

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

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")

Resources