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

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

Related

Add condition to PXSelect

I am trying to add a condition to a PXSelect defined on the SOShipmentEntry graph. I had read that I can just redefine the PXSelect and it would overwrite the original value with mine, but that doesn't seem to be happening.
I created the graph extension as usual, then defined the PXSelect with the extra "And" I need like this:
public PXSelectJoin<SOShipmentPlan,
InnerJoin<SOLineSplit, On<SOLineSplit.planID, Equal<SOShipmentPlan.planID>>,
InnerJoin<SOLine, On<SOLine.orderType, Equal<SOLineSplit.orderType>, And<SOLine.orderNbr, Equal<SOLineSplit.orderNbr>, And<SOLine.lineNbr, Equal<SOLineSplit.lineNbr>>>>,
InnerJoin<InventoryItem, On<InventoryItem.inventoryID, Equal<SOShipmentPlan.inventoryID>>,
LeftJoin<INLotSerClass,
On<InventoryItem.FK.LotSerClass>,
LeftJoin<INSite,
On<SOLine.FK.Site>,
LeftJoin<SOShipLine,
On<SOShipLine.origOrderType, Equal<SOLineSplit.orderType>,
And<SOShipLine.origOrderNbr, Equal<SOLineSplit.orderNbr>,
And<SOShipLine.origLineNbr, Equal<SOLineSplit.lineNbr>,
And<SOShipLine.origSplitLineNbr, Equal<SOLineSplit.splitLineNbr>,
And<SOShipLine.confirmed, Equal<boolFalse>,
And<SOShipLine.shipmentNbr, NotEqual<Current<SOShipment.shipmentNbr>>>>>>>>>>>>>>,
Where<SOShipmentPlan.siteID, Equal<Optional<SOOrderFilter.siteID>>,
And<SOShipmentPlan.planDate, LessEqual<Optional<SOOrderFilter.endDate>>,
And<SOShipmentPlan.orderType, Equal<Required<SOOrder.orderType>>,
And<SOShipmentPlan.orderNbr, Equal<Required<SOOrder.orderNbr>>,
And<SOLine.operation, Equal<Required<SOLine.operation>>,
And<SOShipLine.origOrderNbr, IsNull,
And<SOLineExt.usrShipmentHold, NotEqual<True>>
>>>>>>> ShipmentScheduleSelect;
Edit: After reviewing the code posted by Samvel, I found some additional information online and have created a delegate method below. However, when this runs, I am getting the message that the SiteID is not found, and the Site ID appears to be blank. When I step through the code, PXView seems to contain all of the parameters passed in as expected. It's almost like the parameters are being wiped out. I tried copying the parameters to a new array as well, but that also didn't work. Any ideas?
public IEnumerable shipmentScheduleSelect()
{
PXView select = new PXView(Base, true, Base.ShipmentScheduleSelect.View.BqlSelect);
select.BqlSelect.WhereAnd(typeof(Where<SOLineExt.usrShipmentHold, NotEqual<True>>));
Int32 totalrow = 0;
Int32 startrow = PXView.StartRow;
List<object> result = select.Select(PXView.Currents, PXView.Parameters,
PXView.Searches, PXView.SortColumns, PXView.Descendings,
PXView.Filters, ref startrow, PXView.MaximumRows, ref totalrow);
PXView.StartRow = 0;
return result;
}
Please see below an example of how you can add the data delegate for this specific case:
using System.Collections;
using System.Collections.Generic;
using PX.Data;
using PX.Objects.CS;
using PX.Objects.IN;
namespace PX.Objects.SO
{
public class SOShipmentEntry_Extension : PXGraphExtension<SOShipmentEntry>
{
#region Event Handlers
public IEnumerable shipmentScheduleSelect()
{
throw new PXException("TEST EXCEPTION");
/*
Replace the PXException with the override of the select as you need. Below is just an example how you can do that but make sure to provide all the parameters to the select
var cmd = new PXSelectJoin<SOShipmentPlan, InnerJoin<SOLineSplit, On<SOLineSplit.planID, Equal<SOShipmentPlan.planID>>,
InnerJoin<SOLine, On<SOLine.orderType, Equal<SOLineSplit.orderType>, And<SOLine.orderNbr, Equal<SOLineSplit.orderNbr>, And<SOLine.lineNbr, Equal<SOLineSplit.lineNbr>>>>,
InnerJoin<InventoryItem, On<InventoryItem.inventoryID, Equal<SOShipmentPlan.inventoryID>>,
LeftJoin<INLotSerClass, On<InventoryItem.FK.LotSerialClass>, LeftJoin<INSite, On<SOLine.FK.Site>,
LeftJoin<SOShipLine, On<SOShipLine.origOrderType, Equal<SOLineSplit.orderType>,
And<SOShipLine.origOrderNbr, Equal<SOLineSplit.orderNbr>,
And<SOShipLine.origLineNbr, Equal<SOLineSplit.lineNbr>,
And<SOShipLine.origSplitLineNbr, Equal<SOLineSplit.splitLineNbr>,
And<SOShipLine.confirmed, Equal<boolFalse>,
And<SOShipLine.shipmentNbr, NotEqual<Current<SOShipment.shipmentNbr>>>>>>>>>>>>>>,
Where<SOShipmentPlan.siteID, Equal<Optional<SOOrderFilter.siteID>>,
And<SOShipmentPlan.planDate, LessEqual<Optional<SOOrderFilter.endDate>>,
And<SOShipmentPlan.orderType, Equal<Required<SOOrder.orderType>>,
And<SOShipmentPlan.orderNbr, Equal<Required<SOOrder.orderNbr>>,
And<SOLine.operation, Equal<Required<SOLine.operation>>,
And<SOShipLine.origOrderNbr, IsNull>>>>>>>(this.Base);
List<object> pars = new List<object>(){
siteId,
endDate,
orderType,
orderNbr,
operation
};
bool condition = true;
if(condition)
{
cmd.WhereAnd(typeof(Where<InventoryItem.itemClassID, Equal<Required<InventoryItem.itemClassID>>>));
pars.Add("TEST_ITEM_CLASS");
}
return cmd.Select(pars.ToArray());
*/
}
#endregion
}
}

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

mvc genericly binding byte[] properties

I've tried using a custom model binder but my request.files is not populated. IN forms collection, the input of type file for the byte[] property is populated by file name only.
<input id="collection[#index].#p.Name" name="collection[#index].#p.Name" type="file" />
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace male.services.mvc.Binders
{
public class CustomModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (bindingContext.ModelType.GetProperties().Any(o => o.PropertyType == typeof(byte[])))
{
HttpRequestBase request = controllerContext.HttpContext.Request;
foreach (var pi in bindingContext.ModelType.GetProperties().Where(o => o.PropertyType == typeof(byte[])))
{
// can't access any property in the parameters that gives me my file input or my stream
}
return base.BindModel(controllerContext, bindingContext);
}
else
{
return base.BindModel(controllerContext, bindingContext);
}
}
}
}
I found the answer. This will allow me to go straight from html file inputs to EF models without resorting to hand-crafting a file argument for each model type and property type which needs it. (Aint nobody got time for that)
In order for a form to post files, it must have the enctype attribute:
<form enctype="multipart/form-data" ... />
Once that is done, the file can be accessed via controllerContext.HttpContext.Request.Files
UPDATE - Working Example
Note: You don't need the Regex stuff if you aren't trying to bind a collection. You also don't need the EndsWith, you can just use ==
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Mvc;
namespace male.services.mvc.Binders
{
public class CustomModelBinder : DefaultModelBinder
{
HttpRequestBase request;
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
request = controllerContext.HttpContext.Request;
return base.BindModel(controllerContext, bindingContext);
}
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
{
if (propertyDescriptor.PropertyType == typeof(byte[]))
{
var key = request.Files.AllKeys.SingleOrDefault(o => o.EndsWith($"].{propertyDescriptor.Name}"));
var fileIndex = Regex.Match(key, #"\[(.*)\]").Groups[1].Value;
var modelIndex = Regex.Match(bindingContext.ModelName, #"\[(.*)\]").Groups[1].Value;
if(fileIndex == modelIndex)
{
HttpPostedFileWrapper httpPostedFile = (HttpPostedFileWrapper)request.Files[request.Files.AllKeys.SingleOrDefault(o => o.EndsWith(propertyDescriptor.Name))];
using (MemoryStream ms = new MemoryStream())
{
httpPostedFile.InputStream.CopyTo(ms);
propertyDescriptor.SetValue(bindingContext.Model, ms.ToArray());
}
}
}
else
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
}
}

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