On a PO, I can't seem to get a PO Line's Account field (ExpenseAcctID) to persist. The value appears in the UI normally, and when I check the value in the FieldUpdated, RowUpdated, and RowPersisted handlers, the field actually has a value. Somehow it's not persisting to the db. The field was customized as shown below. The field in the .ASPX is specified as a PXSelector. Any ideas?
[PXMergeAttributes(Method = MergeMethod.Replace)]
[PXRestrictor(typeof(Where<Account.active, Equal<True>>), PX.Objects.GL.Messages.AccountInactive)]
[PXRestrictor(typeof(Where<Where<Current<GLSetup.ytdNetIncAccountID>, IsNull,
Or<Account.accountID, NotEqual<Current<GLSetup.ytdNetIncAccountID>>>>>), PX.Objects.GL.Messages.YTDNetIncomeSelected)]
[PXDimensionSelector(AccountAttribute.DimensionName,
typeof(SearchFor<Account.accountID>
.In<SelectFrom<Account>
.LeftJoin<PMBudget>.On<Account.accountGroupID.IsEqual<PMBudget.accountGroupID>>
.Where<
Where<POLine.lineType.FromCurrent.IsEqual<POLineType.nonStock>
.And<Where<PMBudget.projectID.IsEqual<POLine.projectID.FromCurrent>
.And<Where<PMBudget.projectTaskID.IsEqual<POLine.taskID.FromCurrent>
.And<Where<PMBudget.costCodeID.IsEqual<POLine.costCodeID.FromCurrent>
.And<Where<PMBudget.curyRevisedAmount.IsGreater<Zero>
.And<Where<PMBudget.accountGroupID.IsNotNull>
>>>>>>>>>
.Or<Where<POLine.lineType.FromCurrent.IsNotEqual<POLineType.nonStock>
.Or<Where<POLine.projectID.FromCurrent.IsNull
.Or<Where<POLine.taskID.FromCurrent.IsNull
.Or<Where<POLine.costCodeID.FromCurrent.IsNull
>>>>>>>>>>
.AggregateTo<GroupBy<Account.accountID>>
.OrderBy<Account.accountCD.Asc>>),
typeof(Account.accountCD),
new Type[] { typeof(Account.accountCD), typeof(Account.description), typeof(Account.accountGroupID), typeof(PMBudget.curyRevisedAmount),
typeof(PMBudget.projectID), typeof(PMBudget.projectTaskID), typeof(PMBudget.costCodeID) },
DescriptionField = typeof(Account.description),
Filterable = false
)]
[PXDefault(PersistingCheck = PXPersistingCheck.Nothing)]
[PXUIField(DisplayName = "Account", Visibility = PXUIVisibility.Visible)]
protected void POLine_ExpenseAcctID_CacheAttached(PXCache cache)
{
}
Acumatica needs the attribute [PXDBInt] to relate the field to the database. The original definition of the field in the DAC is:
#region ExpenseAcctID
public abstract class expenseAcctID : PX.Data.BQL.BqlInt.Field<expenseAcctID> { }
protected Int32? _ExpenseAcctID;
[Account(typeof(POLine.branchID),DisplayName = "Account", Visibility = PXUIVisibility.Visible, Filterable = false, DescriptionField = typeof(Account.description), AvoidControlAccounts = true)]
[PXDefault(PersistingCheck = PXPersistingCheck.Nothing)]
public virtual Int32? ExpenseAcctID
{
get
{
return this._ExpenseAcctID;
}
set
{
this._ExpenseAcctID = value;
}
}
#endregion
The [Account] attribute contains the following attributes that are applied for you:
[PXUIField(DisplayName = "Account", Visibility = PXUIVisibility.Visible, FieldClass = DimensionName)]
[PXDBInt]
[PXInt]
[PXRestrictor(typeof(Where<Account.active, Equal<True>>), Messages.AccountInactive)]
[PXRestrictor(typeof(Where<Where<Current<GLSetup.ytdNetIncAccountID>, IsNull,
Or<Account.accountID, NotEqual<Current<GLSetup.ytdNetIncAccountID>>>>>), Messages.YTDNetIncomeSelected)]
By using [PXMergeAttributes(Method = MergeMethod.Replace)] in your cache_attached, all attributes within the DAC definition for that field are dropped and replaced with only what you define.
You switched to [PXDimensionSelector] which does not contain the attribute [PXDBInt]. That means Acumatica has no way of knowing that the field is intended to be tied to the database. You still have access to the field within the screen, but interactions between the cache and the database will skip this field.
To resolve, just add [PXDBInt] to your cache_attached as follows:
[PXMergeAttributes(Method = MergeMethod.Replace)]
[PXDBInt]
[PXRestrictor(typeof(Where<Account.active, Equal<True>>), PX.Objects.GL.Messages.AccountInactive)]
[PXRestrictor(typeof(Where<Where<Current<GLSetup.ytdNetIncAccountID>, IsNull,
Or<Account.accountID, NotEqual<Current<GLSetup.ytdNetIncAccountID>>>>>), PX.Objects.GL.Messages.YTDNetIncomeSelected)]
[PXDimensionSelector(AccountAttribute.DimensionName,
typeof(SearchFor<Account.accountID>
.In<SelectFrom<Account>
.LeftJoin<PMBudget>.On<Account.accountGroupID.IsEqual<PMBudget.accountGroupID>>
.Where<
Where<POLine.lineType.FromCurrent.IsEqual<POLineType.nonStock>
.And<Where<PMBudget.projectID.IsEqual<POLine.projectID.FromCurrent>
.And<Where<PMBudget.projectTaskID.IsEqual<POLine.taskID.FromCurrent>
.And<Where<PMBudget.costCodeID.IsEqual<POLine.costCodeID.FromCurrent>
.And<Where<PMBudget.curyRevisedAmount.IsGreater<Zero>
.And<Where<PMBudget.accountGroupID.IsNotNull>
>>>>>>>>>
.Or<Where<POLine.lineType.FromCurrent.IsNotEqual<POLineType.nonStock>
.Or<Where<POLine.projectID.FromCurrent.IsNull
.Or<Where<POLine.taskID.FromCurrent.IsNull
.Or<Where<POLine.costCodeID.FromCurrent.IsNull
>>>>>>>>>>
.AggregateTo<GroupBy<Account.accountID>>
.OrderBy<Account.accountCD.Asc>>),
typeof(Account.accountCD),
new Type[] { typeof(Account.accountCD), typeof(Account.description), typeof(Account.accountGroupID), typeof(PMBudget.curyRevisedAmount),
typeof(PMBudget.projectID), typeof(PMBudget.projectTaskID), typeof(PMBudget.costCodeID) },
DescriptionField = typeof(Account.description),
Filterable = false
)]
[PXDefault(PersistingCheck = PXPersistingCheck.Nothing)]
[PXUIField(DisplayName = "Account", Visibility = PXUIVisibility.Visible)]
protected void POLine_ExpenseAcctID_CacheAttached(PXCache cache) { }
Related
I am currently trying to add the customer account class to the viewing area of the GL404000 screen with the by extending the GLTran DAC.
The AccountID, AccountCD and Account name are already available so I thought it would be an easy task.
In GLTranR, I saw where they were pulling in the account data:
public new abstract class referenceID : PX.Data.BQL.BqlInt.Field<referenceID> { }
[PXDBInt()]
[PXDimensionSelector("BIZACCT", typeof(Search<BAccountR.bAccountID>), typeof(BAccountR.acctCD), DescriptionField = typeof(BAccountR.acctName), DirtyRead = true)]
[PXUIField(DisplayName = CR.Messages.BAccountCD, Enabled = false, Visible = false)]
public override Int32? ReferenceID
{
get
{
return this._ReferenceID;
}
set
{
this._ReferenceID = value;
}
}
The line that I attempted to change to my need was the [PXDimensionSelector()] however I cannot get this to pull in the class data. Even when I dont change the code at all it will not populate the column.
public new abstract class usrBusinessAccountClass : PX.Data.BQL.BqlInt.Field<usrBusinessAccountClass> { }
protected Int32? _UsrBusinessAccountClass;
[PXDBInt()]
[PXDimensionSelector("BIZACCT", typeof(Search<BAccountR.bAccountID>), typeof(BAccountR.acctCD), DescriptionField = typeof(BAccountR.acctClass), DirtyRead = true)]
[PXUIField(DisplayName = "Business Account Class", Enabled = false, Visible = false)]
public virtual Int32? UsrBusinessAccountClass
{
get {return _UsrBusinessAccountClass;}
set{ _UsrBusinessAccountClass = value;} // set does work but value does not???
}
just for a test I changed the setter to:
set { _UsrBusinessAccountClass = 1234; }
And that populated the column with the value 1234, so that is why I think my issue is just with selecting the class.
I would show this but I need 10 rep to post images.
You are on the proper track. The following code shows how to retrieve additional data to display within the screen.
Non-Database backed field on the DAC you wish to display data on :
public sealed class GLTranRExtension : PXCacheExtension<GLTranR>
{
public abstract class usrClassID : BqlString.Field<usrClassID>
{
}
[PXString(10, IsUnicode = true, InputMask = ">aaaaaaaaaa")]
[PXSelector(typeof(CRCustomerClass.cRCustomerClassID), DescriptionField = typeof(CRCustomerClass.description))]
[PXUIField(DisplayName = "Business Account Class")]
public string UsrClassID { get; set; }
}
Graph extension in which the extension field is then populated with data :
public class AccountByPeriodEnqExtension : PXGraphExtension<AccountByPeriodEnq>
{
protected virtual void __(Events.RowSelecting<GLTranR> e)
{
if(e.Row is GLTranR row)
{
GLTranRExtension rowExt = row.GetExtension<GLTranRExtension>();
using(new PXConnectionScope())
{
BAccount bAccount = PXSelectReadonly<BAccount, Where<BAccount.bAccountID, Equal<Required<BAccount.bAccountID>>>>.Select(this.Base, row.ReferenceID);
if(bAccount != null)
{
rowExt.UsrClassID = bAccount.ClassID;
}
}
}
}
}
You will also need to add within the GL404000 the UI elements for your new extension fields. The result will look as follows :
I created a selector control which displays a list of all serial #s from INItemLotSerial table, it works fine, the problem is the description field is showing InventoryID, how to show InventoryCD. Please have a look at below sample code.
[PXSelector(typeof(Search<INItemLotSerial.lotSerialNbr>),
new Type[] { typeof(INItemLotSerial.lotSerialNbr), typeof(INItemLotSerial.inventoryID) },
SubstituteKey = typeof(INItemLotSerial.lotSerialNbr), DescriptionField = typeof(INItemLotSerial.inventoryID))]
// i also joined with InventoryItem but this doesn't works.
[PXSelector(typeof(Search2<INItemLotSerial.lotSerialNbr,
LeftJoinSingleTable<InventoryItem, On<InventoryItem.inventoryID,Equal<INItemLotSerial.inventoryID>>>>),
new Type[] { typeof(INItemLotSerial.lotSerialNbr), typeof(INItemLotSerial.inventoryID) },
SubstituteKey = typeof(INItemLotSerial.lotSerialNbr), DescriptionField = typeof(InventoryItem.inventoryCD))]
The main problem with DescriptionField property is that it is waiting for getting the field from the same table for which Selector is written. But in the case of ID/CD usually, the CD is not present in the table where ID is present, except the main table.
UPDATED I have removed previous code (implementation using custom attributes and FieldSelecting event handler) because of the performance issues which it is bringing with it. The code below is resulting with the same lookup but getting the data with one inner join instead of all the requests which the previous code was doing.
You can do the following to get this lookup with description:
Create a PXProjection on INItemLotSerial and InventoryItem tables like below:
[PXCacheName("Lot Serials with Inventory CD")]
[PXProjection(typeof(Select2<INItemLotSerial,
InnerJoin<InventoryItem,
On<INItemLotSerial.inventoryID, Equal<InventoryItem.inventoryID>>>>))]
public class INItemLotSerialWithInventoryItem : IBqlTable
{
[PXDBInt(BqlField = typeof(INItemLotSerial.inventoryID))]
[PXUIField(DisplayName = "Inventory ID", Visibility = PXUIVisibility.Visible, Visible = false)]
public virtual int? InventoryID { get; set; }
public abstract class inventoryID : IBqlField { }
[PXDBString(InputMask = "", IsUnicode = true, BqlField = typeof(InventoryItem.inventoryCD))]
[PXUIField(DisplayName = "Inventory ID")]
public virtual string InventoryCD { get; set; }
public abstract class inventoryCD : IBqlField { }
[PXDBString(InputMask = "", IsUnicode = true, BqlField = typeof(INItemLotSerial.lotSerialNbr))]
[PXUIField(DisplayName = "Lot/Serial Nbr")]
public virtual string LotSerialNbr { get; set; }
public abstract class lotSerialNbr : IBqlField { }
}
Set your selector to use this PXProjection like below:
[PXSelector(typeof(Search<INItemLotSerialWithInventoryItem.lotSerialNbr>),
new Type[] { typeof(INItemLotSerialWithInventoryItem.lotSerialNbr) },
SubstituteKey = typeof(INItemLotSerialWithInventoryItem.lotSerialNbr),
DescriptionField = typeof(INItemLotSerialWithInventoryItem.inventoryCD))]
As a result, you will get lookup like below:
I am trying to add INItemXRef's Alternate ID to the global search for Inventory Item profiles. This is a 1:many relationship since there can be various vendors and units of measure with different Alternate ID's.
Per Acumatica, I have tried replacing the NoteID for Inventory Item with a match with join with the below code:
[PXSearchable(SM.SearchCategory.IN, "{0}: {1}", new Type[] {
typeof(InventoryItem.itemType), typeof(InventoryItem.inventoryCD) },
new Type[] { typeof(InventoryItem.descr) },
NumberFields = new Type[] { typeof(InventoryItem.inventoryCD) },
Line1Format = "{0}{1}{2}", Line1Fields = new Type[] {
typeof(INItemClass.itemClassCD), typeof(INItemClass.descr),
typeof(INItemXRef.alternateID) },
Line2Format = "{0}", Line2Fields = new Type[] {
typeof(InventoryItem.descr)},
MatchWithJoin = typeof(RightJoin<INItemXRef, On<INItemXRef.inventoryID,
Equal<InventoryItem.inventoryID>>>),
WhereConstraint = typeof(Where<Current<InventoryItem.itemStatus>,
NotEqual<InventoryItemStatus.unknown>>)
)]
[PXNote(PopupTextEnabled = true)]
I've also followed the example from question How to include field from a linked entity into Full-Text Entity Index?
My code for this in Inventory Item's DAC extension is:
public partial class INItemXRef: PX.Data.IBqlTable
{
//, IPaymentTypeDetailMaster, ILocation
public abstract class inventoryID: IBqlField { }
[PXDBInt()]
[PXDBChildIdentity(typeof(INItemXRef.inventoryID))]
[PXUIField(DisplayName = "Xref ID", Visibility = PXUIVisibility.Invisible)]
[PXSelector(typeof(Search<INItemXRef.inventoryID>), DirtyRead = true)]
public virtual int? InventoryID{ get; set; }
//public virtual void InventoryItem_InventoryID_CacheAttached(PXCache sender)
//{
//}
public abstract class alternateID: IBqlField { }
[PXDBString()]
[PXUIField(DisplayName = "Alternate ID")]
public virtual int? AlternateID{ get; set; }
}
However, the correct way to do this is still and issue. There has been one suggestion to use CacheAttached but I haven't been able to make that work, either.
Is Cache Attached actually required or am I missing something from including linked entities?
The intention is to search for a specific Inventory Item and get the Items and and Alternate ID(s) they have, meaning an Item may appear more than once in the Global Search. I have also made sure to reset IIS and rebuild full text indexes in case this was the issue.
I need to Customize CaseClassID Selector on Cases Screen. It should refresh with different values when Contract field is selected on the CaseScreen under the Additional Info tab. Right now the CaseClass is displaying values from CRCase table.But, now if the contract changes the fieldupdatedEvent should be triggered and CaseClassID selector should have values accordingly. Please suggest me how to customize the CaseClassID selector inside FieldUpdated Event Handler
protected virtual void CRCase_ContractID_FieldUpdated(PXCache sender,
PXFieldUpdatedEventArgs e)
{
CRCase cc = (CRCase)e.Row;
if (cc == null) return;
CRCase_CaseClassID_CacheAttached(sender);
}
[PXMergeAttributes(Method = MergeMethod.Replace)]
[PXDBString(10, IsUnicode = true, InputMask = ">aaaaaaaaaa")]
[PXDefault(typeof(Search<CRSetup.defaultCaseClassID>))]
[PXUIField(DisplayName = "Class ID")]
[PXSelector(typeof(Search2<CRCaseClass.caseClassID,
InnerJoin<CaseContract, On<CaseContract.caseClassID,
Equal<CRCaseClass.caseClassID>>,
InnerJoin<Contract, On<CaseContract.contractID,
Equal<Contract.templateID>>,
InnerJoin<CRCase, On<Contract.contractID,
Equal<Current<CRCase.contractID>>>>>>,
Where<CaseContract.active, Equal<True>>>),
DescriptionField = typeof(CRCaseClass.description),
CacheGlobal = true)]
[PXMassUpdatableField]
public virtual String CaseClassID { get; set; }
public virtual void CRCase_CaseClassID_CacheAttached(PXCache sender){
}
Override CaseClassID selector and add your custom logic in its type parameter.
You can create a custom unbound field to filter the selector according to your business logic. When you want to change selector filter, just change the custom field.
#region CaseClassID
public abstract class caseClassID : IBqlField { }
[PXDBString(10, IsUnicode = true, InputMask = ">aaaaaaaaaa")]
[PXDefault(typeof(Search<CRSetup.defaultCaseClassID>))]
[PXUIField(DisplayName = "Class ID")]
// Use your custom field (filterCaseClassID) in the selector type parameter
[PXSelector(typeof(Search<CRCaseClass.caseClassID,
Where<CRCaseClass.caseClassID, Equal<filterCaseClassID>>>),
DescriptionField = typeof(CRCaseClass.description),
CacheGlobal = true)]
[PXMassUpdatableField]
public virtual String CaseClassID { get; set; }
#endregion
#region FilterCaseClassID
public abstract class filterCaseClassID : IBqlField { }
[PXDBString(10, IsUnicode = true, InputMask = ">aaaaaaaaaa")]
// Change the value of your custom field to set the selector filter
public virtual String FilterCaseClassID { get; set; }
#endregion
EDIT overriding DAC field in graph extension and filtering Selector by using a field from another DAC:
using PX.Data;
using PX.Objects.CR.MassProcess;
using System;
namespace PX.Objects.CR
{
[Serializable]
public class CaseContract : IBqlTable
{
public abstract class caseClassID : IBqlField { }
[PXString(10, IsUnicode = true)]
[PXUIField(DisplayName = "Case Class ID")]
[PXSelector(typeof(CRCaseClass.caseClassID),
DescriptionField = typeof(CRCaseClass.description))]
public virtual String CaseClassID
{
get; set;
}
}
public class CRCaseMaint_Extension : PXGraphExtension<CRCaseMaint>
{
[PXMergeAttributes(Method = MergeMethod.Replace)]
[PXDBString(10, IsUnicode = true, InputMask = ">aaaaaaaaaa")]
[PXDefault(typeof(Search<CRSetup.defaultCaseClassID>))]
[PXUIField(DisplayName = "Class ID")]
[PXSelector(typeof(Search<CRCaseClass.caseClassID,
Where<Current<CaseContract.caseClassID>, IsNull,
Or<CRCaseClass.caseClassID, Equal<Current<CaseContract.caseClassID>>>>>),
DescriptionField = typeof(CRCaseClass.description),
CacheGlobal = true)]
[PXMassUpdatableField]
public virtual void CRCase_CaseClassID_CacheAttached(PXCache sender)
{
}
}
}
I need to create a Contact lookup in SO screen (SO301000). I have already created user defined custom field as below. I is listing all contacts but it is not refreshing based on when select customer. Do I have to write any event for CustomerID to refresh these Contact lookup? Does anyone has any idea?
[PXDBInt]
[PXUIField(DisplayName = "Contact")]
[PXSelector(typeof(Search2<Contact.contactID,
LeftJoin<BAccount, On<BAccount.bAccountID, Equal<Contact.bAccountID>>>>),
DescriptionField = typeof(Contact.displayName), Filterable = true, DirtyRead = true)]
[PXRestrictor(typeof(Where<Contact.isActive, Equal<True>>), PX.Objects.CR.Messages.ContactInactive, typeof(Contact.displayName))]
[PXDBChildIdentity(typeof(Contact.contactID))]
public virtual int? UsrCustContactID { get; set; }
public abstract class usrCustContactID : IBqlField { }
You are missing Where Clause and you need to use BAccount2 instead of BAccount. SOOrderEntry Graph has data view defined with Vendor DAC which gets initialized first/before BAccount and framework will substitute it with Vendor DAC. To prevent this, you need to use BAccount2 DAC in your BQL.
using System;
using PX.Data;
using PX.Objects.SO;
using PX.Objects.CR;
namespace DemoPkg
{
public class SOOrderPXExt : PXCacheExtension<SOOrder>
{
#region UsrContactID
public abstract class usrContactID : IBqlField { }
[PXDBInt()]
[PXUIField(DisplayName = "Contact", Visibility = PXUIVisibility.Visible)]
[PXSelector(typeof(Search2<Contact.contactID,
LeftJoin<BAccount2, On<BAccount2.bAccountID, Equal<Contact.bAccountID>>>>),
DescriptionField = typeof(Contact.displayName), Filterable = true, DirtyRead = true)]
[PXDefault(PersistingCheck = PXPersistingCheck.Nothing)]
[PXFormula(typeof(Default<CRCase.customerID>))]
[PXRestrictor(typeof(Where<Contact.contactType, NotEqual<ContactTypesAttribute.bAccountProperty>,
And<Where<BAccount2.bAccountID, Equal<Current<SOOrder.customerID>>,
Or<Current<SOOrder.customerID>, IsNull>>>>), PX.Objects.CR.Messages.ContactBAccountDiff)]
[PXRestrictor(typeof(Where<Contact.isActive, Equal<True>>), PX.Objects.CR.Messages.ContactInactive,
typeof(Contact.displayName))]
public virtual Int32? UsrContactID { get; set; }
#endregion
}
}
And make sure you have AutoRefresh set to true in aspx for PXSelector control of this field.
I see you do not have a where condition in your selector to be based on the given customer. Try updating your selector to match something like this...
[PXSelector(typeof(Search2<Contact.contactID,
LeftJoin<BAccount, On<BAccount.bAccountID, Equal<Contact.bAccountID>>>,
Where<BAccount.bAccountID, Equal<Current<SOOrder.customerID>>>>),
DescriptionField = typeof(Contact.displayName), Filterable = true, DirtyRead = true)]
In your page also make sure your selector has AutoRefresh="true"
Example:
<px:PXSelector ID="edWeightUOM" runat="server"
DataField="WeightUOM" Size="S" AutoRefresh="true" />