I want to show which user created Invoice, for this i have added default Acumatica field, but the label is showing as Created By, how can i change that label name to "Invoice Created By". Please have a look at below screenshot for field am referring to.
You could use PXUIFieldAttribute.SetDisplayName static method to change DAC field’s display name. This change will be applicable only for Sales Invoice Entry Graph (SO303000 screen)
public class SOInvoiceEntryPXDemoExt : PXGraphExtension<SOInvoiceEntry>
{
public override void Initialize()
{
PXUIFieldAttribute.SetDisplayName<ARInvoice.createdByID>(Base.Document.Cache, "Invoice Created By");
}
}
If you need display name changed for this field in all screens, you need to have DAC Extension as below:
With this, attributes specified in an extension DAC apply to DAC class in every Graph of the application unless a Graph replaces them with other attributes.
public class ARInvoicePXDemoExt : PXCacheExtension<ARInvoice>
{
[PXMergeAttributes(Method = MergeMethod.Append)]
[PXUIField(DisplayName = "Invoice Created By", Enabled = false, IsReadOnly = true)]
public virtual Guid? CreatedByID { get; set; }
}
You need to add CreatedByID field on screen SO303000
And set DisplayMode property to Text.
The 'CreatedByID' I believe is an audit field and therefore cannot easily change the ui of the additional data fields available through the control. The resolution I would suggest is a non database backed UI field that is populated during row selecting. Example can be found below :
public class SOOrderEntryExtension : PXGraphExtension<SOOrderEntry>
{
public virtual void SOOrder_RowSelecting(PXCache sender, PXRowSelectingEventArgs e)
{
SOOrder row = e.Row as SOOrder;
if(row != null)
{
SOOrderExtension rowExt = PXCache<SOOrder>.GetExtension<SOOrderExtension>(row);
Users user = PXSelectReadonly<Users, Where<Users.pKID, Equal<Required<Users.pKID>>>>.Select(this.Base, row.CreatedByID);
if(user != null)
{
rowExt.InvoiceCreatedBy = user.DisplayName;
}
}
}
}
public class SOOrderExtension : PXCacheExtension<SOOrder>
{
public abstract class invoiceCreatedBy : PX.Data.IBqlField
{
}
[PXString]
[PXUIField(DisplayName = "Invoice Created By")]
public virtual string InvoiceCreatedBy { get; set; }
}
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 have a requirement on which the Ship-to Contact & Address in Shipments screen (for Transfer only) will be overridden by using the location of Customer selected from UDF.
The UDF that I have created
A piece of code that I used to test:
#region AddressLine1
[PXMergeAttributes(Method = MergeMethod.Merge)]
[PXDBScalar(typeof(Search2<SOAddress.addressLine1,
InnerJoin<SOShipment, On<SOAddress.customerID, Equal<SOShipment.customerID>>,
InnerJoin<BAccount, On<BAccount.bAccountID, Equal<SOShipment.customerID>>,
InnerJoin<SOShipmentKvExt, On<SOShipment.noteID, Equal<SOShipmentKvExt.recordID>>>>>,
Where<BAccount.acctCD, Equal<Current<SOShipmentKvExt.valueString>>>>))]
public string AddressLine1 { get; set; }
#endregion
Using the above code, the Address Line 1 field remains empty despite the customer in UDF being selected.
I would appreciate an easy-to-understand explanation since I'm fairly new in C#. Thanks!
Edit:
I managed to populate the Ship-to-Contact and Ship-to-Address fields by using the following code:
My DAC Extension:
public class SOShipmentExt : PXCacheExtension<PX.Objects.SO.SOShipment>
{
#region UsrCustomerID
[CustomerActive(DescriptionField = typeof(Customer.acctName))]
[PXUIField(DisplayName = "Customer ID")]
[PXDefault(PersistingCheck = PXPersistingCheck.Nothing)]
public virtual int? UsrCustomerID { get; set; }
public abstract class usrCustomerID : PX.Data.BQL.BqlInt.Field<usrCustomerID> { }
#endregion
#region UsrShipAddressID
[PXDBInt()]
[PXUIField(DisplayName = "Ship Address ID")]
public virtual Int32? UsrShipAddressID { get; set; }
public abstract class usrShipAddressID : PX.Data.BQL.BqlInt.Field<usrShipAddressID> { }
#endregion
#region UsrShipContactID
[PXDBInt()]
[PXUIField(DisplayName = "Ship Contact ID")]
public virtual Int32? UsrShipContactID { get; set; }
public abstract class usrShipContactID : PX.Data.BQL.BqlInt.Field<usrShipContactID> { }
#endregion
}
My Graph Extension:
public class SOShipmentEntryExt : PXGraphExtension<SOShipmentEntry>
{
#region Event Handlers
public virtual void SetShipAddressAndContact(SOShipment shipment, int? shipAddressID, int? shipContactID)
{
SOShipmentExt sOShipmentExt = shipment.GetExtension<SOShipmentExt>();
foreach (SOShipmentAddress address in Base.Shipping_Address.Select())
{
if (address.AddressID < 0)
{
Base.Shipping_Address.Delete(address);
}
}
foreach (SOShipmentContact contact in Base.Shipping_Contact.Select())
{
if (contact.ContactID < 0)
{
Base.Shipping_Contact.Delete(contact);
}
}
}
protected virtual void SOShipment_UsrCustomerID_FieldUpdated(PXCache sender, PXFieldUpdatedEventArgs e)
{
SOShipment row = (SOShipment)e.Row;
SOShipmentExt sOShipmentExt = row.GetExtension<SOShipmentExt>();
SOShipmentAddress sOShipment = SelectFrom<SOShipmentAddress>.Where<SOShipmentAddress.customerID.IsEqual<#P.AsInt>>.View.Select(this.Base, sOShipmentExt.UsrCustomerID);
SOShipmentContact sOShipmentContact = SelectFrom<SOShipmentContact>.Where<SOShipmentContact.customerID.IsEqual<#P.AsInt>>.View.Select(this.Base, sOShipmentExt.UsrCustomerID);
if (row != null && sOShipmentExt != null)
{
sOShipmentExt.UsrShipAddressID = sOShipment.AddressID;
sOShipmentExt.UsrShipContactID = sOShipmentContact.ContactID;
}
SetShipAddressAndContact(row, sOShipmentExt.UsrShipAddressID, sOShipmentExt.UsrShipContactID);
#endregion
}
The issues I’m facing now:
Once the document has been saved and I switched to another document, I am unable to access back the previous document that has been saved and it returns below errors:
ShipAddressID - Specified cast is not valid
ShipContactID - Specified cast is not valid
If the selected Customer ID doesn’t have any data in SOShipmentAddress or SOShipmentContact, then it won’t allow me to select the customer. Ideally, I should be able to select the customer and the fields should be auto populated with address details from Address table.
I would appreciate any help since I’m getting really close to solving this.
SOAddress is a table of addresses that are linked to SOs & Shipments. Since this data generally doesn't change very much, the idea is that records will be linked to multiple SOs and shipments. When an address is changed for a Customer, a new record is created in SOAddress. That way, historical data is preserved intact. Thus, records in the table should not be edited. So, rather than thinking about overwriting the address information in SOAddress, you should think about overwriting the ShipAddressID linked to the Shipment to either a new address you create or more likely an already existing one.
I have added custom fields in customization form using the customization form, steps i have added:
1) Go to shipment form and select Transaction Grid.
2) Select Add fields.
3) Select custome and Add 4 fields and save and publish.
4) Add all 4 fields and select used and save.
5) and publish again and all 4 columns are visible.
6) I have added a user fields name Aloow (PXDBBool), UsrPClocation(Location) and UsrPCwarehouse(Site),
and i set the below attribute in warehour and location.
7) But on SOSHipLine_InventotyID_FieldUpdate event, i am setting Allow, location, warehouse all 3 values, but values are not showing in Grid, what is the resoon?
#region UsrQCSiteID
[PXUIField(DisplayName = "PC Warehouse")]
[SiteAvail(typeof(SOShipLine.inventoryID), typeof(SOShipLine.subItemID))]
[PXUIRequired(typeof(Where<usrAllow, Equal<True>>))]
[PXUIEnabled(typeof(Where<usrAllow, Equal<True>>))]
[PXDefault()]
public virtual int? UsrPCSiteID {
get; set;
}
public abstract class usrPCSiteID:PX.Data.BQL.BqlInt.Field<usrPCSiteID> {
}
#endregion UsrPCSiteID
#region UsrPCLocationID
[PXUIField(DisplayName = "PC Location")]
[SOLocationAvail(typeof(SOShipLine.inventoryID), typeof(SOShipLine.subItemID), typeof(SOShipLineExt.usrPCSiteID), typeof(SOLine.tranType), typeof(SOShipLine.invtMult))]
[PXUIRequired(typeof(Where<usrQCRequired, Equal<True>>))]
[PXUIEnabled(typeof(Where<usrQCRequired, Equal<True>>))]
[PXDefault()]
public virtual int? UsrPCLocationID {
get; set;
}
public abstract class usrPCLocationID:PX.Data.BQL.BqlInt.Field<usrPCLocationID> {
}
#endregion UsrPCLocationID
#region Allow
[PXDBBool]
[PXUIField(DisplayName = "Allow")]
public virtual bool? UsrAllow {
get; set;
}
public abstract class usrAllow:PX.Data.BQL.BqlBool.Field<usrAllow> {
}
#endregion
IS SOShipLine allow updating custom values?
The steps that you described look correct.
I recreated this scenario locally:
1 - My DAC extension looks as follows:
public class SOShipLineExt : PXCacheExtension<PX.Objects.SO.SOShipLine>
{
#region UsrPCSiteID
[PXUIField(DisplayName = "PC Warehouse")]
[SiteAvail(typeof(SOShipLine.inventoryID), typeof(SOShipLine.subItemID))]
public virtual int? UsrPCSiteID {
get; set;
}
public abstract class usrPCSiteID:PX.Data.BQL.BqlInt.Field<usrPCSiteID> {
}
#endregion UsrPCSiteID
#region UsrPCLocationID
[PXUIField(DisplayName = "PC Location")]
[SOLocationAvail(typeof(SOShipLine.inventoryID), typeof(SOShipLine.subItemID), typeof(SOShipLineExt.usrPCSiteID), typeof(SOShipLine.tranType), typeof(SOShipLine.invtMult))]
public virtual int? UsrPCLocationID {
get; set;
}
public abstract class usrPCLocationID:PX.Data.BQL.BqlInt.Field<usrPCLocationID> {
}
#endregion UsrPCLocationID
#region Allow
[PXDBBool]
[PXUIField(DisplayName = "Allow")]
public virtual bool? UsrAllow {
get; set;
}
public abstract class usrAllow:PX.Data.BQL.BqlBool.Field<usrAllow> {
}
#endregion
}
Notes about the DAC Extension:
I removed references to PXUIEnabled and PXUIRequired. If you are looking to disable the Site and the Location based on the checkbox's value, I recommend you manage this logic in the RowSelected event. (it may be feasible with your approach but I have not used it before)
The PXDefault references were also removed, given that in most cases the Shipment is created directly from the Sales Order page. As you have it right now, the field is mandatory but no default value is being assigned which will cause an error. You have 2 options: 1) indicate the value in the PXDefault() attribute, or 2) Set the property PXPersistingCheck.Nothing.
Note that your SOLocationAvail attribute has an error in the 4th parameter. You should use typeof(SOShipLine.tranType) instead of typeof(SOLine.tranType). This was generating an error when the shipment was created from the SO.
2 - My FieldUpdated event looks as follows:
public class SOShipmentEntry_Extension : PXGraphExtension<SOShipmentEntry>
{
protected virtual void SOShipLine_InventoryID_FieldUpdated(PXCache cache, PXFieldUpdatedEventArgs e)
{
if (e.Row == null) return;
SOShipLine line = (SOShipLine)e.Row;
SOShipLineExt lineExt = cache.GetExtension<SOShipLineExt>(line);
if (lineExt != null)
{
lineExt.UsrAllow = true;
lineExt.UsrPCSiteID=154;
lineExt.UsrPCLocationID=155;
}
}
}
Notes about the Graph extension:
I hard-coded the Warehouse and Location values. I recommend you do the same on your end while testing is being done, just make sure that the IDs exist in your DB, or that you query the CD value and then use its corresponding ID value.
Results:
When the Shipment is created from a SO, the values are being correctly assigned:
How can I show QtyOnHand in IN402000 to the PO 301000 detail grid?
The POLine DAC already has a "QtyAvail (Qty on Hand)" field. You would simply need to create a customization project, add the screen, navigate to the Grid: Transactions and add the Control for the field to the screen & publish.
If you need to add the field to PO Line and the Quantity Available from INItemStatus would suffice you could add it with:
using System;
using PX.Data;
using PX.Objects.PO;
using PX.Objects.IN;
public class POLine_Extension : PXCacheExtension<POLine> {
[PXDecimal]
[PXUIField(DisplayName = "Qty Avail", Enabled = false)]
[PXDBScalar(typeof(
Search<INSiteStatus.qtyAvail,
Where<INSiteStatus.inventoryID, Equal<POLine.inventoryID>,
And<INSiteStatus.siteID, Equal<POLine.siteID>>>>))]
public virtual decimal? QtyAvail { get; set; }
public abstract class qtyAvail : IBqlField {}
}
Then the PXDBScalar fields have their values set when the DAC is Selected. There's probably a better way this could be done, but to populate the field as lines are added & updated to the PO within a POOrderEntry graph extension, you could handle the FieldUpdated events for the InventoryID and SiteID to execute a function that retrieves the Qty Available using basically the same BQL and sets that value to the DAC extension field.
public class POOrderEntry_Extension : PXGraphExtension<POOrderEntry>
{
public virtual void POLine_InventoryID_FieldUpdated(PXCache sender, PXFieldUpdatedEventArgs e)
{
UpdateQtyAvailable(sender, e);
}
public virtual void POLine_SiteID_FieldUpdated(PXCache sender, PXFieldUpdatedEventArgs e)
{
UpdateQtyAvailable(sender, e);
}
private void UpdateQtyAvailable(PXCache sender, PXFieldUpdatedEventArgs e)
{
if (e.Row == null) return;
POLine row = (POLine)e.Row;
foreach(INSiteStatus siteStatus in PXSelect<INSiteStatus,
Where<INSiteStatus.inventoryID, Equal<Required<INSiteStatus.inventoryID>>,
And<INSiteStatus.siteID, Equal<Required<INSiteStatus.siteID>>>>>.Select(sender.Graph, row.InventoryID, row.SiteID))
{
sender.SetValueExt<POLine_Extension.qtyAvail>(row, siteStatus.QtyAvail);
}
}
}
I want to add the attributes for my inventory lookup on Sales Order and Purchase Order, does anyone know how to? or any ideas?
Please refer below code example to include Attributes Columns in Selector and Grid Control utilizing out-of-box CRAttributesFieldAttribute.
Declare a class PXAddAtttributeColumns inherited from CRAttributesFieldAttribute.
public class PXAddAtttributeColumns : CRAttributesFieldAttribute
{
string[] _names;
bool _IsForSelector;
public PXAddAtttributeColumns(string[] names, Type entityType, Type entityIDField, Type classIDField, bool IsForSelector = true)
: base(entityType, entityIDField, classIDField)
{
_names = names;
_IsForSelector = IsForSelector;
}
public override void CacheAttached(PXCache sender)
{
this._IsActive = true;
base.CacheAttached(sender);
}
protected override void AttributeFieldSelecting(PXCache sender, PXFieldSelectingEventArgs e, PXFieldState state, string attributeName, int idx)
{
if (_names.Any(attributeName.Equals))
{
//Out-of-box DisplayName is prefixed with "$Attributes$-" - if you need to take that off.
state.DisplayName = (!String.IsNullOrEmpty(state.DisplayName)) ? (state.DisplayName.Replace("$Attributes$-", "")) : attributeName;
state.Visible = true;
//Requires AutoGenerateColumns="AppendDynamic" for PXGrid Control for dynamic Attribute columns creation
state.Visibility = (_IsForSelector) ? PXUIVisibility.SelectorVisible : PXUIVisibility.Dynamic;
}
base.AttributeFieldSelecting(sender, e, state, attributeName, idx);
}
public override void CommandPreparing(PXCache sender, PXCommandPreparingEventArgs e)
{
base.CommandPreparing(sender, e);
if (e.BqlTable == null && aggregateAttributes && sender.GetItemType().IsDefined(typeof(PXProjectionAttribute), true))
{
e.BqlTable = _BqlTable;
}
}
}
To include attributes as Columns in Inventory Look up, declare DAC Extension as below:
public class InventoryItemPXExt : PXCacheExtension<PX.Objects.IN.InventoryItem>
{
#region Attributes
public abstract class attributes : IBqlField { }
[PXAddAtttributeColumns(new[] { "ASSETID", "HWMODEL" },
typeof(CSAnswerType.inventoryAnswerType),
typeof(InventoryItem.inventoryID),
typeof(InventoryItem.itemClassID))]
public virtual string[] Attributes { get; set; }
#endregion
}
Fields will show up as below:
Search can be Enabled on Attribute Columns by setting FilterByAllFields to True
To include attributes as Columns in Sales Order Details Grid, declare DAC Extension as below:
public class SOLineExtension : PXCacheExtension<SOLine>
{
public abstract class itemAttributes : IBqlField { }
[PXAddAtttributeColumns(new[] { "ASSETID", "HWMODEL" },
typeof(CSAnswerType.inventoryAnswerType),
typeof(SOLine.inventoryID),
typeof(InventoryItem.itemClassID), false)]
public virtual string[] ItemAttributes { get; set; }
}
Make sure to specify AutoGenerateColumns="AppendDynamic" for PXGrid control dynamic Attribute columns creation
Fields will show up as below:
To include attributes as Columns in grid of Add Stock Item Dialog, declare DAC Extension as below:
public class SOSiteStatusSelectedExtension : PXCacheExtension<SOSiteStatusSelected>
{
public abstract class itemAttributes : IBqlField { }
[PXAddAtttributeColumns(new[] { "ASSETID", "HWMODEL" },
typeof(CSAnswerType.inventoryAnswerType),
typeof(InventoryItem.inventoryID),
typeof(InventoryItem.itemClassID), false)]
public virtual string[] ItemAttributes { get; set; }
}
Make sure to specify AutoGenerateColumns="AppendDynamic" for PXGrid control dynamic Attribute columns creation
Fields will show up as below:
Note: This example is applicable to 5.3 series – Build 5.30.1367 onwards.
Updated solution for versions later than 20R1:
Below is the sample scenario to add custom attributes to both CustomerID and InventoryID lookups of Sales Orders (SO301000) screen.
Create the required attributes from Attributes (CS205000) screen.
Add the necessary attribute to the corresponding Customer Classes (AR201000) screen to be added to the CustomerID lookup and to the corresponding Item Classes (IN201000) screen to be added to the InventoryID lookup.
Create a new Attribute class CustomerAttributeSetFieldsAttribute extending CustomerActiveAttribute (since it is used in CustomerID) and override SetColumns by rewriting the same in order to change the FieldList and HeaderList.
public class CustomerAttributeSetFieldsAttribute : CustomerActiveAttribute
{
public static void SetColumns(PXCache cache, string field, string[] fieldList, string[] headerList)
{
PXSelectorAttribute.SetColumns(cache, field, fieldList, headerList);
foreach (CustomerAttributeSetFieldsAttribute attr in cache.GetAttributes(field).OfType<CustomerAttributeSetFieldsAttribute>())
{
attr._FieldList = fieldList;
attr._HeaderList = headerList;
}
}
}
Override Initialize method and call Activate for activating the attribute by passing proper cache - BAccountR - for CustomerID and InventoryItem - for InventoryID. Also, call SetColumns from the custom attribute for CustomerID CustomerAttributeSetFieldsAttribute and PXSelectorAttribute for InventoryID and pass the cache type (where the lookup is present), field (field name), fieldList (list of names of all the fields including the attributes) and headerList (list of names of all the headers including the attributes).
Override the attributes to merge the newly added attribute to CustomerID by adding CustomerAttributeSetFields to SOOrder_CustomerID_CacheAttached method.
public class SOOrderEntry_Extension : PXGraphExtension<SOOrderEntry>
{
public override void Initialize()
{
base.Initialize();
//for InventoryID lookup
CR.CRAttributesFieldAttribute.Activate(Base.Caches[typeof(InventoryItem)]);
PXSelectorAttribute.SetColumns(Base.Caches[typeof(SOLine)],
"InventoryID", new string[] { "InventoryCD", "Descr", "itemClassID", "Color_ATTRIBUTES" },
new string[] { "InventoryCD", "Descr", "itemClassID", "$Attributes$-Color" });
//for CustomerID lookup
CR.CRAttributesFieldAttribute.Activate(Base.Caches[typeof(BAccountR)]);
CustomerAttributeSetFieldsAttribute.SetColumns(Base.Caches[typeof(SOOrder)],
"CustomerID", new string[] { "AcctCD", "acctName", "COMPREV_Attributes" },
new string[] { "AcctCD", "acctName", "$Attributes$-Company Revenue" });
}
[PXMergeAttributes(Method = MergeMethod.Merge)]
[CustomerAttributeSetFields]
protected virtual void SOOrder_CustomerID_CacheAttached(PXCache sender)
{
}
}
Note: Since Customer DAC is derived from BAccount and CustomerActive attribute is derived from Customer, it has the cache type BAccountR and only the attributes which are of the same cache type are available to add to the CustomerID popup. If the user needs to add other attributes of different cache type, it will be better to create a new Attribute and use that instead of CustomerActive.