In Acumatica, there are instances where a DAC for a screen (e.g., Projects) is not directly bound to a table (PMProject), but inherits a DAC that IS bound to a table (Contract). Is there a good instructional reference source for doing this?
This theme is partially covered in T200 training course(Example 9.1).
Basic principles:
If you inherited some class DAC2 from the DAC1 bound to the SQL table then DAC2 will also be bound to the same SQL table.
[Serializable]
public partial class DAC1 : IBQLTable
{
public abstract class tableID : PX.Data.IBqlField
{
}
[PXDBIdentity()]
public virtual Int32? TableID { get; set;}
}
[Serializable]
public partial class DAC2 : DAC1
{}
However, fields from DAC1 will be used in the generated SQL query.
PXSelect<DAC2, Where<DAC2.tableID, Equal<Required<DAC2.tableID>>>(...) ->
SELECT [DAC2].[TableID] FROM DAC1 DAC2 Where [DAC1].[TableID] = #P0
To allow BQL generate SQL queries with DAC2 field you should replace abstract class of this field in DAC2
[Serializable]
public partial class DAC2 : DAC1
{
public new abstract class tableID : PX.Data.IBqlField
{
}
}
SQL query will look like that:
SELECT [DAC2].[TableID] FROM DAC1 DAC2 Where [DAC2].[TableID] = #P0
To override attributes of the field you should override corresponding property in DAC2
[Serializable]
public partial class DAC2 : DAC1
{
public new abstract class tableID : PX.Data.IBqlField
{
}
[PXDBIdentity()]
[PXUIField(DisplayName = "ID")]
public override Int32? TableID {get; set;}
}
If you want DAC2 to be different from DAC1, for instance you want to add some fields to DAC2 but you also want to keep DAC1 unmodified then you can use PXTable attribute(e.g. ARInvoice class)
[PXTable]
[Serializable]
public partial class DAC2 : DAC1
{
public new abstract class tableID : PX.Data.IBqlField
{
}
[PXDBIdentity()]
[PXUIField(DisplayName = "ID")]
public override Int32? TableID {get; set;}
public abstract class description : PX.Data.IBqlField
{
}
[PXDBString(60)]
public virtual String Description{get; set;}
}
The SQL query will look like that:
SELECT [DAC2].[TableID], [DAC2.Description]
FROM (SELECT [DAC1].[TableID] as TableID, [DAC2].[Description] as Description
FROM DAC1 Inner Join DAC2 on DAC1.TableID=DAC2.TableID) DAC2
Where [DAC2].[TableID] = #P0
Related
Below is extension DAC and Graph code, INRegisterKitAssemblyExt is a DAC, i have created a database table for that. i want to save usrSiteID value in INRegisterKitAssemblyExt(separate) database table. Please check how may i initialize extension DAC with INKitRegister DAC and save the value and how to use usrSiteId in UI?
[PXTable(typeof(INKitRegister.refNbr), typeof(INKitRegister.KitRevisionId))]
public class INRegisterKitAssemblyExt: PXCacheExtension<INKitRegister> {
#region RefNbr
public abstract class refNbr:PX.Data.BQL.BqlString.Field<refNbr> {
}
protected String _RefNbr;
[PXDBDefault(typeof(INKitRegister.refNbr))]
[PXDBString()]
[PXParent(typeof(Select<INKitRegister, Where<INKitRegister.refNbr, Equal<Current<refNbr>>,And<INKitRegister.kitRevisionID, Equal<Current<kitRevisionID>>>>>))]
public virtual String RefNbr {
get {
return this._RefNbr;
}
set {
this._RefNbr = value;
}
}
#endregion
#region KitRevisionID
public abstract class kitRevisionID:PX.Data.BQL.BqlString.Field<kitRevisionID> {
}
protected String _KitRevisionID;
[PXDBDefault(typeof(INKitRegister.kitRevisionID))]
[PXDBString()]
public virtual String KitRevisionID {
get {
return this._KitRevisionID;
}
set {
this._KitRevisionID = value;
}
}
#endregion
// My Fields as well as refNbr and KitRevison is also here
public virtual int? UsrQCSiteID {
get; set;
}
public abstract class usrQCSiteID:PX.Data.BQL.BqlInt.Field<usrQCSiteID> {
}
}
// Extension Graph
public class KitAssemblyEntryExt:PXGraphExtension<KitAssemblyEntry> {
}
INRegisterKitAssemblyExt in your code example is not a DAC.
DAC inherits from the IBqlTable base class.
INRegisterKitAssemblyExt in your code example is a DAC extension.
DAC extensions inherits from the PXCacheExtension base class.
You should never create database table for DAC extensions.
Custom fields in DAC extension will be persisted to the base DAC table automatically.
The main issue here is that INKitRegister is not DAC bound to a database table.
It's a Projection DAC and the INKitRegister database table does not exist.
The INKitRegister DAC projection points to the INRegister DAC:
[PXPrimaryGraph(typeof(KitAssemblyEntry))]
[PXCacheName(Messages.INKit)]
[PXProjection(typeof(Select2<INRegister, InnerJoin<INTran, On<INRegister.FK.KitTran>>>), Persistent=true)]
[Serializable]
public partial class INKitRegister : IBqlTable, ILSPrimary
You should create an extension on the INRegister DAC instead of INKitRegister because INRegister is bounded to an actual database table of the same name:
public class INRegisterKitAssemblyExt: PXCacheExtension<INRegister>
I have a customer utilizing the User Defined fields. I found that the values are located in KvExt tables in the database, but I have not found a way to access those directly through DACs or DAC extensions. Is there a way I can access that field and add it to a base Acumatica page?
The specific target in my case is the ARTran.RefNbr selector in the ARPayments page.
As you probably know the User Defined Fields are actually using the Attributes defined in the system. The User Defined Field and actual record (for example ARInvoice) are bound by NoteID of the record (ARInvoice) and RecordID of the User Defined Field. And the FieldName of the User Defined Field is storing word 'Attribute' + AttributeID.
Below SQL Queries show that references:
SELECT CompanyID, RefNbr, DocType, DocDesc FROM ARRegister WHERE RefNbr='AR007092';
SELECT CompanyID, RecordID, FieldName, ValueNumeric, ValueDate, ValueString, ValueText FROM ARRegisterKvExt where RecordID='78A9D6DE-52C4-E911-B2FD-FC017C8C8936';
SELECT CompanyID, AttributeID, Description, ControlType, EntryMask, RegExp, List, IsInternal, ContainsPersonalData FROM CSAttribute WHERE'AttributeBURDEN' LIKE '%'+AttributeID;
And the result sets:
After knowing the references shown above, we can create a DAC to the ARRegisterKvExt table like below:
[PXCacheName("AR Register Attributes")]
[Serializable]
public class ARRegisterKvExt : IBqlTable
{
public abstract class recordID : BqlGuid.Field<recordID> { }
[PXDBGuid(IsKey = true)]
public Guid? RecordID { get; set; }
public abstract class fieldName : BqlString.Field<fieldName> { }
[PXDBString(50,IsKey = true)]
[PXUIField(DisplayName ="Name")]
public string FieldName { get; set; }
public abstract class valueNumeric : BqlDecimal.Field<valueNumeric> { }
[PXDBDecimal(8)]
[PXUIField(DisplayName = "Value Numeric")]
public decimal? ValueNumeric { get; set; }
public abstract class valueDate : BqlDateTime.Field<valueDate> { }
[PXDBDate]
[PXUIField(DisplayName = "Value Date")]
public DateTime? ValueDate { get; set; }
public abstract class valueString : BqlString.Field<valueString> { }
[PXDBString(256)]
[PXUIField(DisplayName = "Value String")]
public string ValueString { get; set; }
public abstract class valueText : BqlString.Field<valueText> { }
[PXDBString]
[PXUIField(DisplayName = "Value Text")]
public string ValueText { get; set; }
}
And write a PXSelector with Left Joins to our DAC and CSAttribute like below:
[PXSelector(typeof(Search2<ARInvoice.refNbr, LeftJoin<ARRegisterKvExt, On<ARInvoice.noteID,Equal<ARRegisterKvExt.recordID>>, LeftJoin<CSAttribute, On<ARRegisterKvExt.fieldName,Contains<CSAttribute.attributeID>>>>>), new[] { typeof(ARInvoice.refNbr), typeof(ARInvoice.docType), typeof(CSAttribute.description), typeof(ARRegisterKvExt.valueString)}, DescriptionField = typeof(ARInvoice.docDesc))]
As a result, you will see lookup like below:
As you can see the AR Invoices which don't have value for the User Defined Fields don't have the Name of the field too. This is caused by the way Acumatica is handling the User Defined Fields. The record for the User Defined Fields is being written to the database only when you set a value. And deletes that record if you clear the value.
The bad side of using the User Defined Fields in the lookups is that if you have 2 User Defined Fields for AR Invoice then the same Invoice will be shown twice, but only if the values for both User Defined Fields are set.
I have multiple branches/Locations for Vendors and need it as parameter but when I tried to populate the lookup it returns empty.
Report Parameter
Location View name I used:
=Report.GetFieldSchema('Location.LocationCD')
The result
You can reuse an existing DAC but for more control you may create a Parameter DAC
using PX.Data;
using PX.Objects.CS;
using PX.Objects.GL;
using PX.Objects.CR;
using System;
namespace PX.Objects.AP
{
public class APVendorParameters : IBqlTable
{
#region ActiveVendorID
public abstract class activeVendorID : PX.Data.IBqlField { }
[Vendor]
[PXRestrictor(typeof(Where<Vendor.status, NotEqual<BAccount.status.inactive>>),
Messages.VendorIsInStatus,
typeof(Vendor.status))]
public virtual int? ActiveVendorID { get; set; }
#endregion
#region VendorLocationID
public abstract class vendorLocationID : PX.Data.IBqlField { }
[PXDBInt]
[PXSelector(typeof(Search<Location.locationID, Where<Location.bAccountID, Equal<Optional<activeVendorID>>>>), SubstituteKey = typeof(Location.locationCD), DescriptionField = typeof(Location.locationCD))]
[PXUIField(DisplayName = "Location")]
public virtual Int32? VendorLocationID { get; set; }
#endregion
}
}
In report add 2 parameters:
VendorID (string) ViewName=Report.GetFieldSchema('APVendorParameters.ActiveVendorID')
LocationID (string) ViewName=Report.GetFieldSchema('APVendorParameters.VendorLocationID,VendorID')
The secret stais in
,VendorID')
syntax
I've added Customer Location to the Full-Text Entity Index, but cannot figure out how to get Address Line 1 from the Location to be part of the Full-Text Index and be displayed in the result.
To includes fields of linked entities (those which are in one-to-one relationship with the top-level entity on a data entry screen), it's required to specify what top-level entity field should be used along with the PXSelectorAttribute to retrieve the linked entity. Right after the top-level entity field acting as a bridge between the linked entities, you will specify fields of the secondary entity, which should be included into the Full-Text Index and/or be displayed in the result. Keep in mind, that only top-level entity fields decorated with PXSelectorAttribute or PXDimensionSelectorAttribute have the ability to act as a bridge between the linked entities.
For example, to include fields from the Address DAC into the Customer Location Full-Text Entity Index, you must add the DefAddressID field from the Location DAC before listing fields from the Address DAC:
public partial class Location : PX.Data.IBqlTable, IPaymentTypeDetailMaster, ILocation
{
...
public abstract class defAddressID : IBqlField { }
[PXDBInt()]
[PXDBChildIdentity(typeof(Address.addressID))]
[PXUIField(DisplayName = "Default Address", Visibility = PXUIVisibility.Invisible)]
[PXSelector(typeof(Search<Address.addressID>), DirtyRead = true)]
public virtual int? DefAddressID { get; set; }
...
}
The CustomerLocation DAC found in the following code snippet can serve as a perfect example of a custom DAC used to add Customer Location to the Full-Text Entity Index:
[Serializable]
[PXCacheName("Customer Location")]
[PXBreakInheritance]
public partial class CustomerLocation : SelectedCustomerLocation
{
public new abstract class bAccountID : IBqlField { }
[Customer(typeof(Search<Customer.bAccountID,
Where<Customer.type, Equal<BAccountType.customerType>,
Or<Customer.type, Equal<BAccountType.prospectType>,
Or<Customer.type, Equal<BAccountType.combinedType>>>>>),
IsKey = true)]
public override int? BAccountID { get; set; }
public new abstract class locationCD : IBqlField { }
public new abstract class descr : IBqlField { }
public new abstract class defAddressID : IBqlField { }
public new abstract class locType : IBqlField { }
public new abstract class noteID : IBqlField { }
[PXNote()]
[PXSearchable(SM.SearchCategory.CR, "{1} {2}: {3}",
new Type[] {
typeof(CustomerLocation.bAccountID),
typeof(Customer.acctCD),
typeof(CustomerLocation.locationCD),
typeof(CustomerLocation.descr) },
new Type[] {
typeof(CustomerLocation.bAccountID),
typeof(Customer.acctCD),
typeof(CustomerLocation.locationCD),
typeof(CustomerLocation.descr),
typeof(CustomerLocation.defAddressID),
typeof(Address.addressLine1),
typeof(Address.addressLine2),
typeof(Address.city),
typeof(Address.countryID) },
Line1Format = "{0} {2}",
Line1Fields = new Type[] {
typeof(CustomerLocation.descr),
typeof(CustomerLocation.defAddressID),
typeof(Address.addressLine1) },
Line2Format = "{1}",
Line2Fields = new Type[] {
typeof(CustomerLocation.defAddressID),
typeof(Address.addressLine2) },
WhereConstraint =
typeof(Where<CustomerLocation.locType, Equal<LocTypeList.customerLoc>,
Or<CustomerLocation.locType, Equal<LocTypeList.combinedLoc>>>),
MatchWithJoin = typeof(InnerJoin<Customer,
On<Customer.bAccountID, Equal<CustomerLocation.bAccountID>>>),
SelectForFastIndexing = typeof(Select2<CustomerLocation,
InnerJoin<Customer,
On<CustomerLocation.bAccountID, Equal<Customer.bAccountID>>>>)
)]
public override Guid? NoteID { get; set; }
}
Becides the DefAddressID field, which is used to include fields from the Address DAC to the Full-Text Entity Index, CustomerLocation also utilize CustomerAttribute attached to the BAccountID field to include Customer's natural application-wise AcctCD keys instead of the surrogate DB-level BAccountID keys. Last thing to mention is the PXBreakInheritanceAttribute required to prevent initialization of PXCache objects corresponding to base DACs when on Rebuild Full-Text Entity Index screen the system generates list of entities to be used by Full-Text Entity Index.
I use PxProjection attribute in a way that select the specific fields of DACs. How should I pass filter parameter in the select command(the same way as it is in the graph by using Or<CRInsurancePolicy.currentreportsTo, Equal<Current<BAccountParam.bAccountID>>> ) in order to have filtered data in the graph?
I propose to look at Select2 attribute in PXProjection.
Select2 attribute allows to use filter - all possible commands of BQL (Join, Where, GroupBy, OrderBy).
For examle here, the attribute joins data from two table and projects it to the single DAC:
[Serializable]
[PXProjection(typeof(Select2<Supplier,
InnerJoin<SupplierProduct,
On<SupplierProduct.accountID, Equal<Supplier.accountID>>>>))]
public partial class SupplierPrice : IBqlTable
{
public abstract class accountID : PX.Data.IBqlField
{
}
// The field mapped to the Supplier field (through setting of BqlField)
[PXDBInt(IsKey = true, BqlField = typeof(Supplier.accountID))]
public virtual int? AccountID { get; set; }
public abstract class productID : PX.Data.IBqlField
{
}
// The field mapped to the SupplierProduct field
// (through setting of BqlField)
[PXDBInt(IsKey = true, BqlField = typeof(SupplierProduct.productID))]
[PXUIField(DisplayName = "Product ID")]
public virtual int? ProductID { get; set; }
...
}