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
}
}
Related
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();
}
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 hide a field while copy and past. The field is part of the extension of the sales order.DAC.
I have tried [PXCopyPasteHiddenFields(typeof(PSSOOrderExtNV.usrIsInHandsDate))] and I am getting the following compilation error.
Error CS0592 Attribute 'PXCopyPasteHiddenFields' is not valid on this declaration type. It is only valid on 'class, field' declarations.
I have tried to Override the method CopyPasteGetScript I did not get the desired result.
public delegate void CopyPasteGetScriptDelegate(Boolean isImportSimple, List<Command> script, List<Container> containers);
[PXOverride]
public void CopyPasteGetScript(Boolean isImportSimple, List<Command> script, List<Container> containers, CopyPasteGetScriptDelegate baseMethod)
{
baseMethod(isImportSimple, script, containers);
SOOrder order = Base.Document.Current;
if(Base.Document.Cache.GetStatus(order) == PXEntryStatus.Inserted)
{
PSSOOrderExtNV extn = PXCache<SOOrder>.GetExtension<PSSOOrderExtNV>(order);
extn.UsrHoldUntil = null;
extn.UsrReadyforProductionapproval = null;
extn.UsrReadyForProduction = null;
extn.UsrIsOrdered = null;
extn.UsrIsAllocated = null;
extn.UsrEmbPaperReceived = null;
extn.UsrEmbGoodsReceived = null;
extn.UsrWorksheetPrinted = null;
extn.UsrGoodsOnCarts = null;
Base.Document.Update(order);
}
}
Update
I have modified the code as bellow in graph extension of SOOrderEntry. It is not giving error while compiling, but it is copying the values to new order.
[PXCopyPasteHiddenFields(typeof(SOOrder.cancelled), typeof(SOOrder.preAuthTranNumber), typeof(SOOrder.ownerID), typeof(SOOrder.workgroupID),
typeof(PSSOOrderExtNV.usrHoldUntil),typeof(PSSOOrderExtNV.usrReadyForProduction),typeof(PSSOOrderExtNV.usrReadyforProductionapproval),typeof(PSSOOrderExtNV.usrIsOrdered),
typeof(PSSOOrderExtNV.usrIsAllocated),typeof(PSSOOrderExtNV.usrEmbPaperReceived),typeof(PSSOOrderExtNV.usrEmbGoodsReceived),typeof(PSSOOrderExtNV.usrWorksheetPrinted),
typeof(PSSOOrderExtNV.usrGoodsOnCarts))]
public PXSelect<SOOrder, Where<SOOrder.orderType, Equal<Current<SOOrder.orderType>>, And<SOOrder.orderNbr, Equal<Current<SOOrder.orderNbr>>>>> CurrentDocument;
PXCopyPasteHiddenFields attribute typically decorates DataViews.
This example in Sales Order graph, hides the SOLine.Completed field from the Transactions DataView:
[PXViewName(Messages.SOLine)]
[PXImport(typeof(SOOrder))]
[PXCopyPasteHiddenFields(typeof(SOLine.completed))]
public PXOrderedSelect<SOOrder, SOLine,
Where<SOLine.orderType, Equal<Current<SOOrder.orderType>>,
And<SOLine.orderNbr, Equal<Current<SOOrder.orderNbr>>>>,
OrderBy<Asc<SOLine.orderType, Asc<SOLine.orderNbr, Asc<SOLine.sortOrder, Asc<SOLine.lineNbr>>>>>> Transactions;
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));
}
}
}
We have a problem with SharePoint's search box. Whenever we try to search for something we get:
Unable to validate data. at
System.Web.Configuration.MachineKeySection.EncryptOrDecryptData(Boolean
fEncrypt, Byte[] buf, Byte[] modifier,
Int32 start, Int32 length, IVType
ivType, Boolean useValidationSymAlgo)
at
System.Web.UI.ObjectStateFormatter.Deserialize(String
inputString) exeption.
Does any one know the reason for this exception, or a way to work around it?
New entry:
I'm using a SPGridView where i use the datakeys property in a web part. The webpart works, but we found that using the datakeys property breaks seach in that if you try to use the search textbox and click the seach button it gets this exception:
Unable to validate data. at System.Web.Configuration.MachineKeySection.EncryptOrDecryptData(Boolean fEncrypt, Byte[] buf, Byte[] modifier, Int32 start, Int32 length, IVType ivType, Boolean useValidationSymAlgo)
at System.Web.UI.ObjectStateFormatter.Deserialize(String inputString)
This is what i have tryed to do:
Make the gridview not spgridview and set autogenerate true (works)
Remove the datakeynames (works)
Test with a empty gridvew (failes)
Test with a non-empty gridview (failes)
Change Machine Keys (failes)
Turn of view state on the gridvew (failes)
Move the gridview ti a ascx file (failes)
I can't seem to figure this one out. Have enyone got this error and been able to work around it?
EDit 10.09.2009
This is the last code I tested. I used a MSDN excample as referance. I have also tried without Data table MSDN Example
public class TestErrorGridView : System.Web.UI.WebControls.WebParts.WebPart
{
Control ascxToAdd;
protected DataTable PropertyCollection = new DataTable();
private DataColumn key;
public TestErrorGridView()
{
key = PropertyCollection.Columns.Add("ID", typeof(string));
PropertyCollection.Columns.Add("Name", typeof(string));
}
public void AddProperty(TestBindObject data)
{
DataRow newRow = PropertyCollection.Rows.Add();
newRow["ID "] = data.ID;
newRow["Name"] = data.Name;
}
public void BindGrid(SPGridView grid)
{
SPBoundField fldPropertyName = new SPBoundField();
fldPropertyName.HeaderText = "ID";
fldPropertyName.DataField = "ID";
grid.Columns.Add(fldPropertyName);
SPBoundField fldPropertyValue = new SPBoundField();
fldPropertyValue.HeaderText = "Name";
fldPropertyValue.DataField = "Name";
grid.Columns.Add(fldPropertyValue);
PropertyCollection.PrimaryKey = new DataColumn[] { key };
grid.DataSource = PropertyCollection.DefaultView;
grid.DataKeyNames = new string[] { key.ColumnName };
grid.DataBind();
}
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
}
protected override void CreateChildControls()
{
base.CreateChildControls();
TestBindObject t1 = new TestBindObject() { ID = 1, Name = "Test3" };
this.AddProperty(t1);
SPGridView testGrid = new SPGridView() { AutoGenerateColumns = false };
this.BindGrid(testGrid);
this.Controls.Add(testGrid);
}
}
[Serializable]
public class TestBindObject
{
public int ID { get; set; }
public string Name { get; set; }
}
From the error message I'd say that you are operating in a web-farm environment. Have you set the same machineKey in each of the SharePoint web.config files? See this link for a little more info.