Returning a dataset / array from a Generic Inquiry in BLC / graph code - acumatica

I need to obtain a dataset or array of the results provided by a Generic Inquiry inside of BLC / graph logic.
I've been given the following as an example, but this obviously only returns a count.
public class SOOrderEntryExt : PXGraphExtension<SOOrderEntry>
{
public PXAction<SOOrder> Test;
[PXButton]
[PXUIField]
protected void test()
{
var dataGraph = PXGenericInqGrph.CreateInstance("GI000010");
if (dataGraph != null)
{
var count = dataGraph.Views["Results"].SelectMulti().Count;
}
}
}
When I use an index in the returned variable, I don't get anything that resembles a row of the actual data in the GI, for example:
dataGraph.Views["Results"].SelectMulti()[0]
This doesn't return the actual data. I've tried the several methods / properties provided by the dataGraph.Views["Results"] object, but none give me the results I need.
Is there a method or property that simply returns a dataset or array of the actual results of the Generic Inquiry?

The following custom button I developed for Non-Stock Items will call the specified Generic Inquiry, update its parameter value and retrieve all result rows and corresponding field results without needing to know the specific DAC.
public class NonStockItemMaintExtension : PXGraphExtension<NonStockItemMaint>
{
#region Buttons
public PXAction<InventoryItem> RunSummary;
[PXButton]
[PXUIField(DisplayName = "Run Summary")]
public virtual IEnumerable runSummary(PXAdapter adapter)
{
InventoryItem item = Base.Item.Current;
GIDesign design = PXSelectReadonly<GIDesign, Where<GIDesign.name, Equal<Required<GIDesign.name>>>>.Select(this.Base, "ItemTrans");
if (design != null)
{
PXLongOperation.StartOperation(this.Base, delegate ()
{
//Creates Generic Inquiry Graph for the specified inquiry
PXGenericInqGrph graph = PXGenericInqGrph.CreateInstance(design.DesignID.Value);
if (design != null)
{
//Sets parameter value
graph.Caches[typeof(GenericFilter)].SetValueExt(graph.Filter.Current, "Item", item.InventoryCD);
//Loops through each returned result row of Generic Inquiry
foreach (GenericResult resultRow in graph.Views["Results"].SelectMulti())
{
//Loops through objects returned from one - not an object per field
foreach (string key in resultRow.Values.Keys)
{
//Loops through list of each object and the fields we need values from for each data key
foreach (GIResult resultMap in PXSelectReadonly<GIResult, Where<GIResult.designID, Equal<Required<GIResult.designID>>, And<GIResult.objectName, Equal<Required<GIResult.objectName>>>>>.Select(graph, new object[] { design.DesignID.Value, key }))
{
//retrieves field value from data object specified
var result = graph.Caches[resultRow.Values[key].GetType()].GetValue(resultRow.Values[key], resultMap.Field);
}
}
}
}
});
}
return adapter.Get();
}
#endregion
}
Tables referenced in Generic Inquiry :
Data fields to retrieve from the inquiry :
Parameters being populated :
Value being retrieved :

This will give you a list of the results. Each list element will contain the records involved with 1 row of the GI result (each joined table). Only those fields included in the GI will have values I believe.
public PXAction<SOOrder> Test;
[PXButton]
[PXUIField]
protected void test()
{
// Using "Invoiced Items" (GI000008) GI in 2017R2
var dataGraph = PXGenericInqGrph.CreateInstance("GI000008");
if (dataGraph == null)
{
return;
}
var resultList = dataGraph.Results.Select().FirstTableItems.ToList();
foreach (GenericResult genericResult in resultList)
{
// Note: not all values are pulled into the DAC, only those used in the query
var arInvoice = GetDac<PX.Objects.AR.ARInvoice>(genericResult);
var arTran = GetDac<PX.Objects.AR.ARTran>(genericResult);
var customer = GetDac<PX.Objects.AR.Customer>(genericResult);
var customerClass = GetDac<PX.Objects.AR.Customer>(genericResult);
var address = GetDac<PX.Objects.AR.Customer>(genericResult);
var bAccount = GetDac<PX.Objects.CR.BAccount>(genericResult);
var inventoryItem = GetDac<PX.Objects.IN.InventoryItem>(genericResult);
var formulaValues = genericResult.Values.Last();
}
}
protected virtual T GetDac<T>(GenericResult genericResult) where T : class, PX.Data.IBqlTable
{
// Example:
//var customer = (PX.Objects.AR.Customer)genericResult.Values["Customer"]
return (T)(genericResult?.Values != null &&
genericResult.Values.ContainsKey(typeof(T).Name)
? genericResult.Values[typeof(T).Name]
: null);
}

Related

Is there a way to iterate through the fields in a row of a PXResultSet?

Is it possible to use a foreach loop in a BLC to iterate through the fields of a PXResultSet to get the FieldNames?
Is this doable? I can't seem to find a good way.
Thanks...
The PXResultset records are selected from a view. You can get the field names from the View.
Here's a full example:
public class SOOrderEntry_Extension : PXGraphExtension<SOOrderEntry>
{
public override void Initialize()
{
// Get field list from data view
var dataView = new PXSelect<SOOrder>(Base);
string fieldNames = string.Join(",", GetFieldNames(dataView.View, Base.Caches));
// You don't need result set to get field names
PXResultset<SOOrder> resultSet = dataView.Select();
throw new PXException(fieldNames);
}
public string[] GetFieldNames(PXView view, PXCacheCollection caches)
{
var list = new List<string>();
var set = new HashSet<string>();
foreach (Type t in view.GetItemTypes())
{
if (list.Count == 0)
{
list.AddRange(caches[t].Fields);
set.AddRange(list);
}
else
{
foreach (string field in caches[t].Fields)
{
string s = String.Format("{0}__{1}", t.Name, field);
if (set.Add(s))
{
list.Add(s);
}
}
}
}
return list.ToArray();
}
}
When run, this example will show the fields names used in the data view in Sales Order screen SO301000 as an exception.
Field names are contained in Cache object. If you really need to get field names from PXResultset you need to iterate the cache types in the result set.
Example for first DacType (0) of result set:
public class SOOrderEntry_Extension : PXGraphExtension<SOOrderEntry>
{
public override void Initialize()
{
var dataView = new PXSelect<SOOrder>(Base);
PXResultset<SOOrder> resultSet = dataView.Select();
foreach (PXResult result in resultSet)
{
Type dacType = result.GetItemType(0);
foreach (var field in Base.Caches[dacType].Fields)
PXTrace.WriteInformation(field);
}
}
}

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

Acumatica - Need Help in Updating Activities in Project Quotes Screen PM304500 through custom action

I have a custom action on screen CR304000 - OpportunityMaint in the Quotes tab of the grid view that marks a field called IsPrimary in the CRQuote DAC as true for the current record in the Quotes view. These project Quotes are associated with the current opportunity as well as with a related PMQoute in the PMQuoteMaint BLC. The PMQuoteMaint BLC has a view called Activties that has all the CRActivities associated with the PMQuote. I created a custom field in the CRActivity called IsPrimary and added it to the Activities view in the PMQuoteMaint BLC grid.
My goal was to override the Action in the OpportunityMaint to update the IsPrimary field in CRActivity to true or false depending on what the Action in OpportunityMaint is toggling. However, my IsPrimary field in PMQuoteMaint is not toggling. My code attempts to get Current Quote OpportunityMain and then create a PMQuoteMaint graph and set the Current record. Then iterate though Activies view and set the IsPrimary field accordingly. Like I said, not having success because I'm not sure that my code is successfully retrieving the correct Activities.
There might be a better way to access CRActivity associated with a PMQuote, but I'm not sure. Any help would be appreciated. Here is code:
public virtual IEnumerable PrimaryQuote(PXAdapter adapter)
{
foreach (CROpportunity opp in adapter.Get())
{
if (Quotes.Current?.IsPrimary != true)
{
var selectExistingPrimary = new PXSelect<CRQuote, Where<CRQuote.quoteID,
Equal<Required<CRQuote.quoteID>>>>(this);
CRQuote primary = selectExistingPrimary.Select(opp.DefQuoteID);
if (primary != null && primary.QuoteID != Quotes.Current.QuoteID && primary.Status ==
PM.PMQuoteStatusAttribute.Closed)
{
throw new PXException(PM.Messages.QuoteIsClosed, opp.OpportunityID,
primary.QuoteNbr);
}
var quoteID = Quotes.Current.QuoteID;
var opportunityID = this.Opportunity.Current.OpportunityID;
this.Persist();
PXDatabase.Update<Standalone.CROpportunity>(
new PXDataFieldAssign<Standalone.CROpportunity.defQuoteID>(quoteID),
new PXDataFieldRestrict<Standalone.CROpportunity.opportunityID>(PXDbType.VarChar,
255, opportunityID, PXComp.EQ)
);
this.Cancel.Press();
CROpportunity rec = this.Opportunity.Search<CROpportunity.opportunityID>
(opportunityID);
yield return rec;
}
yield return opp;
}
``` My OverRide
public PXAction<CROpportunity> primaryQuote;
[PXUIField(DisplayName = Messages.MarkAsPrimary)]
[PXButton]
public virtual IEnumerable PrimaryQuote(PXAdapter adapter)
{
// this is currently selected record in quotes grid
var currQuoteNbr = Base.Quotes.Current.QuoteNbr;
bool isPrimary2;
foreach (CRQuote quote in Base.Quotes.Select())
{
var quoteNbr = quote.QuoteNbr;
if(quoteNbr.Trim() == currQuoteNbr.Trim())
{
isPrimary2 = true;
}
else
{
isPrimary2 = false;
}
PXTrace.WriteInformation(string.Format("Quote: {0} Value:
{1}",quoteNbr.ToString(),isPrimary2.ToString()));
var PMQuoteMaintGraph = PXGraph.CreateInstance<PMQuoteMaint>();
PMQuoteMaintGraph.Quote.Current = PMQuoteMaintGraph.Quote.Search<PMQuote.quoteNbr>
(quoteNbr.Trim()); // this is the current quote
foreach (CRActivity activity in PMQuoteMaintGraph.Activities.Select())
{
CRActivityExt itemExt = PXCache<CRActivity>.GetExtension<CRActivityExt>(activity);
itemExt.UsrPrimary = isPrimary2;
PMQuoteMaintGraph.Activities.Cache.Persist(PXDBOperation.Update);
PXDatabase.Update<CRActivity>(new PXDataFieldAssign<CRActivityExt.usrPrimary>
(isPrimary2));
}
}
return Base.primaryQuote.Press(adapter);
}

Copy User Fields from CRM Quote to Sales Order

I am trying to copy some user fields from the CRM Quote to the Sales Order. The CRM Quote uses a different object than the Sales Quote and there doesn't appear to be a way to relate it back. I tried overriding the Create Sales Order to add a handler, but this didn't seem to work Any help would be appreciated. Here is the code I tried:
public class OpportunityMaint_Extension : PXGraphExtension<OpportunityMaint>
{
public delegate IEnumerable CreateSalesOrderDelegate(PXAdapter adapter);
[PXOverride]
public virtual IEnumerable CreateSalesOrder(PXAdapter adapter, CreateSalesOrderDelegate baseMethod)
{
Base.RowInserting.AddHandler<SOLine>((sender, e) =>
{
SOLine orderLine = e.Row as SOLine;
if (orderLine == null) return;
SOLineExt orderLineExt = orderLine.GetExtension<SOLineExt>();
var product = Base.Products.Current;
CROpportunityProductsExt productExt = product.GetExtension<CROpportunityProductsExt>();
orderLineExt.UsrHasAnticipatedDiscount = productExt.UsrHasAnticipatedDiscount;
orderLineExt.UsrAnticipatedDiscountPct = productExt.UsrAnticipatedDiscountPct;
orderLineExt.UsrAnticipatedDiscountAmt = productExt.UsrAnticipatedDiscountAmt;
orderLineExt.UsrAnticipatedUnitPrice = productExt.UsrAnticipatedUnitPrice;
orderLineExt.UsrTotalAnticipatedDiscountAmt = productExt.UsrTotalAnticipatedDiscountAmt;
});
return baseMethod(adapter);
}
}
Thanks!
There are two posts with answers to this same question:
Populate custom field while creating sale order from opportunity
How to pass custom field vales from Opportunity to sales Order?
To sum it up, you can add a rowinserting event handler within the button action or my preference is within DoCreateSalesOrder (extending OpportunityMaint) like the example below...
[PXOverride]
public virtual void DoCreateSalesOrder(OpportunityMaint.CreateSalesOrderFilter param, Action<OpportunityMaint.CreateSalesOrderFilter> del)
{
PXGraph.InstanceCreated.AddHandler<SOOrderEntry>(graph =>
{
graph.RowInserting.AddHandler<SOLine>((cache, args) =>
{
var soLine = (SOLine)args.Row;
if (soLine == null)
{
return;
}
CROpportunityProducts opProduct = PXResult<CROpportunityProducts>.Current;
if (opProduct == null)
{
return;
}
var opProductExt = PXCache<CROpportunityProducts>.GetExtension<CROpportunityProductsExt>(opProduct);
var soLineExt = PXCache<SOLine>.GetExtension<SOLineExt>(soLine);
//Copy all extension fields here...
});
});
del(param);
}

Attributes Field Not Showing Attribute Value on All Pages

I have a field which shows the attribute of an item called "Coating." I added this field via the Layout Editor to two existing screens I am customizing: The Sales Price and Sales Price Worksheets pages. On the Sales Price Worksheet page, the coatings show up just fine:
However, on the Sales Price page, they do not:
I have the exact same element in both page customization in the Layout Editor contained in the respected grids: InventoryItem__COATING_Attributes. Checking the Attributes tab of the element, they both have the same code:
protected string[] _Attributes;
/// <summary>
/// Reserved for internal use.
/// Provides the values of attributes associated with the item.
/// For more information see the <see cref="CSAnswers"/> class.
/// </summary>
[CRAttributesField(typeof(InventoryItem.itemClassID))]
As far as I can tell, everything is exactly the same. I even checked the results of the query that is sent for both pages' select statements and they are properly returning similar statements to show the coatings for each element.
Any ideas on why this isn't working?
I assume the attribute works as expected on the Sales Price Worksheets screen due to the sample posted in another thread and the lack of delegate declared for the ARPriceWorksheetMaint.Details data view:
public class ARPriceWorksheetMaint : PXGraph<ARPriceWorksheetMaint, ARPriceWorksheet>
{
...
[PXImport(typeof(ARPriceWorksheet))]
public PXSelectJoin<ARPriceWorksheetDetail,
LeftJoin<InventoryItem, On<InventoryItem.inventoryID, Equal<ARPriceWorksheetDetail.inventoryID>>>, Where<ARPriceWorksheetDetail.refNbr, Equal<Current<ARPriceWorksheet.refNbr>>>,
OrderBy<Asc<ARPriceWorksheetDetail.priceType, Asc<ARPriceWorksheetDetail.priceCode, Asc<InventoryItem.inventoryCD, Asc<ARPriceWorksheetDetail.breakQty>>>>>> Details;
...
}
On the Sales Price screen though, there is a records() delegate returning only instances of the ARSalesPrice DAC, preventing the attribute from obtaining its values from database:
public virtual IEnumerable records()
{
...
foreach (PXResult<ARSalesPrice> res in QSelect(this, Records.View.BqlSelect, new object[] { filter.PriceType, filter.PriceType, filter.PriceType == PriceTypes.Customer ? priceCode : null, filter.PriceType == PriceTypes.CustomerPriceClass ? priceCode : null, priceCode, filter.InventoryID, filter.InventoryID, filter.EffectiveAsOfDate, filter.EffectiveAsOfDate, filter.EffectiveAsOfDate, filter.ItemClassID, filter.ItemClassID, filter.InventoryPriceClassID, filter.InventoryPriceClassID, filter.OwnerID, filter.OwnerID, filter.MyWorkGroup, filter.WorkGroupID, filter.WorkGroupID }))
{
ARSalesPrice price = res;
yield return price;
}
...
}
In order to show the attribute on the Sales Price screen, you should modify the Records data view and override its delegate to return instances of both the ARSalesPrice and InventoryItem DACs:
public class ARSalesPriceMaintExt : PXGraphExtension<ARSalesPriceMaint>
{
[PXFilterable]
public PXSelectJoin<ARSalesPrice,
LeftJoin<InventoryItem, On<InventoryItem.inventoryID, Equal<ARSalesPrice.inventoryID>>>,
Where<InventoryItem.itemStatus, NotEqual<INItemStatus.inactive>,
And<InventoryItem.itemStatus, NotEqual<INItemStatus.toDelete>,
And2<Where<Required<ARSalesPriceFilter.priceType>, Equal<PriceTypes.allPrices>, Or<ARSalesPrice.priceType, Equal<Required<ARSalesPriceFilter.priceType>>>>,
And2<Where<ARSalesPrice.customerID, Equal<Required<ARSalesPriceFilter.priceCode>>, Or<ARSalesPrice.custPriceClassID, Equal<Required<ARSalesPriceFilter.priceCode>>, Or<Required<ARSalesPriceFilter.priceCode>, IsNull>>>,
And2<Where<ARSalesPrice.inventoryID, Equal<Required<ARSalesPriceFilter.inventoryID>>, Or<Required<ARSalesPriceFilter.inventoryID>, IsNull>>,
And2<Where2<Where2<Where<ARSalesPrice.effectiveDate, LessEqual<Required<ARSalesPriceFilter.effectiveAsOfDate>>, Or<ARSalesPrice.effectiveDate, IsNull>>,
And<Where<ARSalesPrice.expirationDate, GreaterEqual<Required<ARSalesPriceFilter.effectiveAsOfDate>>, Or<ARSalesPrice.expirationDate, IsNull>>>>,
Or<Required<ARSalesPriceFilter.effectiveAsOfDate>, IsNull>>,
And<Where2<Where<Required<ARSalesPriceFilter.itemClassID>, IsNull,
Or<Required<ARSalesPriceFilter.itemClassID>, Equal<InventoryItem.itemClassID>>>,
And2<Where<Required<ARSalesPriceFilter.inventoryPriceClassID>, IsNull,
Or<Required<ARSalesPriceFilter.inventoryPriceClassID>, Equal<InventoryItem.priceClassID>>>,
And2<Where<Required<ARSalesPriceFilter.ownerID>, IsNull,
Or<Required<ARSalesPriceFilter.ownerID>, Equal<InventoryItem.priceManagerID>>>,
And2<Where<Required<ARSalesPriceFilter.myWorkGroup>, Equal<False>,
Or<InventoryItem.priceWorkgroupID, InMember<CurrentValue<ARSalesPriceFilter.currentOwnerID>>>>,
And<Where<Required<ARSalesPriceFilter.workGroupID>, IsNull,
Or<Required<ARSalesPriceFilter.workGroupID>, Equal<InventoryItem.priceWorkgroupID>>>>>>>>>>>>>>>,
OrderBy<Asc<ARSalesPrice.inventoryID,
Asc<ARSalesPrice.priceType,
Asc<ARSalesPrice.uOM, Asc<ARSalesPrice.breakQty, Asc<ARSalesPrice.effectiveDate>>>>>>> Records;
public IEnumerable records()
{
var startRow = PXView.StartRow;
int totalRows = 0;
foreach (ARSalesPrice salesPrice in Base.Records.View.Select(PXView.Currents, PXView.Parameters, PXView.Searches,
PXView.SortColumns, PXView.Descendings, PXView.Filters, ref startRow, PXView.MaximumRows, ref totalRows))
{
var item = PXSelectorAttribute.Select<ARSalesPrice.inventoryID>(Records.Cache, salesPrice) as InventoryItem;
var res = new PXResult<ARSalesPrice, InventoryItem>(salesPrice, item);
yield return res;
}
PXView.StartRow = 0;
}
}

Resources