In the Sales Order screen, there is this view :
public PXSelect<SiteStatus> sitestatusview;
And SiteStatus table is declared as this :
[SiteStatusAccumulator]
[PXDisableCloneAttributes()]
[Serializable]
public partial class SiteStatus : INSiteStatus, IQtyAllocated
{...
...}
SiteStatusAccumulator among its usage is to check available quantities. And I want to override this validation. Particularly the SiteStatusAccumulator.PersistInserted method. With fields, I can cache_attached and override the attributes. But with tables, how can you achieve something similar ?
Related
Is it possible to have a different tag name fo my user control?
My class looks like:
public sealed partial class ValidationControl : UserControl { ... }
and used like this:
<controls:ValidationControl Model="{x:Bind ViewModel, Mode=OneWay}" PropertyName="Name"/>
Is there some property in my user control that could change the tag name but that my class' name will continue naming ValidationControl?:
<controls:Validation Model="{x:Bind ViewModel, Mode=OneWay}" PropertyName="Name"/>
I have another class named Validation, that's why I cannot name only Validation to my control.
You could create a one-liner marker type:
public class AlternativeName : ValidationControl { }
...and instantiate this one instead of the base class:
<controls:lternativeName ...
Don't forget to also remove the sealed keyword from the definition of the existing ValidationControl class:
public partial class ValidationControl : UserControl { ... }
I have a request to change the number sequencer for a new Item based on the Class that is selected for the new item. So, the user selects a particular class and the system need to find the next value for the InventoryItem.InventoryCD by using a number sequence that is set on a field in a preferences form.
Any ideas on how to do this?
I've tried the FieldDefaulting() event for the InventoryCD and InventoryID fields but, it's not working.
TIA!
A custom numbering attribute is required to achieve the functionality you desire. Please find working example below.
First we define the fields on setup which will hold our custom numbering sequences :
public class INSetupExtension : PXCacheExtension<INSetup>
{
#region InventoryNumbering1ID
public abstract class inventoryNumbering1ID : PX.Data.BQL.BqlString.Field<inventoryNumbering1ID>
{
}
[PXDBString(10, IsUnicode = true)]
[PXDefault("INV1")]
[PXSelector(typeof(Numbering.numberingID), DescriptionField = typeof(Numbering.descr))]
[PXUIField(DisplayName = "Inventory Numbering One", Visibility = PXUIVisibility.Visible)]
public virtual String InventoryNumbering1ID { get; set; }
#endregion
#region InventoryNumbering1ID
public abstract class inventoryNumbering2ID : PX.Data.BQL.BqlString.Field<inventoryNumbering2ID>
{
}
[PXDBString(10, IsUnicode = true)]
[PXDefault("INV2")]
[PXSelector(typeof(Numbering.numberingID), DescriptionField = typeof(Numbering.descr))]
[PXUIField(DisplayName = "Inventory Numbering Two", Visibility = PXUIVisibility.Visible)]
public virtual String InventoryNumbering2ID { get; set; }
#endregion
}
Next we define our custom Autonumbering attribute, here I have made the "default" numbering sequence the argument for the constructor declaration.
In the overridden RowPersisting event we utilize our criteria to determine the numbering sequence to use for the next persisted new item.
In my example I use one sequence if an item class is used, if no item class is selected I use another.
public class NonStockAutoNumbereAttribute : AutoNumberAttribute
{
public NonStockAutoNumbereAttribute() : base(typeof(INSetupExtension.inventoryNumbering2ID), typeof(AccessInfo.businessDate))
{
}
public override void RowPersisting(PXCache sender, PXRowPersistingEventArgs e)
{
InventoryItem row = e.Row as InventoryItem;
if(sender.GetStatus(row) == PXEntryStatus.Inserted)
{
base.RowPersisting(sender, e);
INSetup setup = PXSelectBase<INSetup, PXSelectReadonly<INSetup>.Config>.Select(sender.Graph, Array.Empty<object>());
INSetupExtension setupExt = setup.GetExtension<INSetupExtension>();
string nextNumber;
object newValue = sender.GetValue(e.Row, this._FieldOrdinal);
if(!string.IsNullOrEmpty((string)newValue))
{
INItemClass itemClass = PXSelectReadonly<INItemClass, Where<INItemClass.itemClassID, Equal<Required<INItemClass.itemClassID>>>>.Select(sender.Graph, row.ItemClassID);
if(itemClass != null)
{
nextNumber = AutoNumberAttribute.GetNextNumber(sender, e.Row, setupExt.InventoryNumbering1ID, DateTime.Now);
}
else
{
nextNumber = AutoNumberAttribute.GetNextNumber(sender, e.Row, setupExt.InventoryNumbering2ID, DateTime.Now);
}
sender.SetValue(e.Row, this._FieldName, nextNumber);
}
}
}
}
On the Inventory Item maintenance page I have added a simple CacheAttached :
public class NonStockItemMaintExtension : PXGraphExtension<NonStockItemMaint>
{
[PXDefault]
[NonStockAutoNumbereAttribute]
[InventoryRaw(typeof(Where<InventoryItem.stkItem.IsEqual<False>>), IsKey = true, DisplayName = "Inventory ID", Filterable = true)]
protected virtual void _(Events.CacheAttached<InventoryItem.inventoryCD> e) { }
}
The results can be seen below :
No class numbering
Class Numbering
You mentioned trying to do this on FieldDefaulting and also that you attempted on InventoryID and InventoryCD. Before we get into a couple of possible options, let's focus on the nature of InventoryID vs. InventoryCD and the FieldDefaulting event. Also, we need to consider the impact of AutoNunmber on InventoryCD.
InventoryID vs. InventoryCD
InventoryID is an Identity field. It is a sequence generated by the database on insert to allow us to uniquely identify the record even if we change other common values such as the InventoryCD, as you desire. The InventoryCD field is also known as the Inventory Code. This is the human readable number that adheres to our desired nomenclature for defining a part number. For instance, we use a 2 digit year to prefix a number sequence to clearly indicate how old the part number is. Because InventoryID is an identity field, NEVER try to update that field, or you may cause data integrity issues for all the related tables that point back to the InventoryItem record via the InventoryID field.
FieldDefaulting and SetDefaultExt
FieldDefaulting may be the place you want to set the value for InventoryCD. However, you don't know the value at the time you are creating the record. In fact, you don't even know the value when you are ready to press the save button. Unless you have configured numbering to allow manual entry, the number isn't assigned until the Persist. As the value is inserted into the database, the AutoNumber logic fires to identify the numbering sequence to generate the InventoryCD value. If you wish to use the FieldDefaulting event, you must first enable manual numbering for the Inventory numbering sequence. You then want to be sure to use SetDefaultExt on the FieldUpdated event of the item class field so that when you change item classes, you get a new inventory id. You also must decide if this value is allowed to change once assigned because the aforementioned logic will change the value every time you change the item class unless you put other measures in place.
Change ID Action
It is important to note that there is a Change ID action on the Action menu. It is intended for this very purpose, although it is a manual entry. I'd recommend that you explore the code behind that action to understand how Acumatica goes about altering the InventoryCD. Also, note that this is performed manually after the record has been created and and InventoryCD has been assigned already. Perhaps you have a way to identify that the InventoryCD value is still the default and needs to be changed, so you could do something around overriding Persist.
Custom Attribute Inheriting AutoNumber
If you are more adventurous than I am, you could consider a custom Attribute inheriting from the AutoNumber attribute and use a cacheattached to change to your attribute.
Business Events to Initiate Custom Action
Finally, you might consider a business event to watch for when an InventoryItem record has been inserted and then fire some custom action that simulates the Change ID action but using your logic for determining the new number based on the Item Class. If you only capture when the record is created and not when it is updated, then I suspect you can use the business event to make the initial InventoryCD change per Item Class and then require manual change through Change ID if needed again later.
Each of these points gives you some possible ways to approach making the change you need as well as some potential hazards. While this doesn't give you the code achieve your objective, I hope that it gives you some options to find the solution that works best for you. Please post a comment or answer once you reach a solution that works best for you.
I have a need to override the Select statement being used for the SOShipmentPlan PXProjection/DAC, namely, removing the
And <INPlanType.isFixed, Equal<boolFalse>
condition.
I can override all of the CreateShipment() logic and bring in any other necessary routines into an SOShipmentEntry_Extension class, to the point where I finally can use my own version of a SOShipmentPlan class, but that all seems needlessly complex when all I want to do is override the select for the PXProjection attribute. Overriding CreateShipment() and supporting routines also seems like a quick way to get in trouble come time for upgrades.
So, is there an easy way to override the PXProjection's BQL, or am I stuck overriding all kinds of code?
UPDATE 1
Based on a link provided below (stackoverflow.com/a/41540659/7376238), I feel like I'm close. Here's the block of code I end up with:
namespace PX.Objects.SO
{
public class SOShipmentEntry_Extension : PXGraphExtension<SOShipmentEntry>
{
#region Event Handlers
#endregion
[Serializable]
[PXProjection(typeof(Select2<SOOrder,
InnerJoin<SOOrderType, On<SOOrder.FK.OrderType>,
InnerJoin<INItemPlan, On<INItemPlan.refNoteID, Equal<SOOrder.noteID>>,
InnerJoin<INPlanType, On<INItemPlan.FK.PlanType>>>>,
Where<INItemPlan.hold, Equal<boolFalse>,
And<INItemPlan.planQty, Greater<decimal0>,
And<INPlanType.isDemand, Equal<boolTrue>,
And<INPlanType.isForDate, Equal<boolTrue>,
And<Where<INItemPlan.fixedSource, IsNull,
Or<INItemPlan.fixedSource, NotEqual<INReplenishmentSource.transfer>>>>>>>>>))]
[PXSubstitute()]
public partial class SOShipmentPlanCst : SOShipmentPlan
{
int x = 0;
}
}
But it doesn't seem to work. Not sure of where I'm supposed to put the code. I've tried putting the class definition inside and outside of public class SOShipmentEntry_Extension : PXGraphExtension<SOShipmentEntry> class (currently inside the extension class as shown). No luck either way.
THIS ANSWER DIDN'T WORK
Fair warning... I have not done this to a PXProjection before, so you'll have to see if this works. The nature of extensions tends to allow overriding views by simply redefining them. I have not done this myself with a projection, but I suspect it will be similar. Give it a try and see if you get the desired results. All I can say about testing it is that "it compiled" when I added to my project and removed the INItemPLanType.isFixed condition.
public class SOShipmentEntry_Extension : PXGraphExtension<SOShipmentEntry>
{
[PXProjection(typeof(Select2<SOOrder,
InnerJoin<SOOrderType, On<SOOrder.FK.OrderType>,
InnerJoin<INItemPlan, On<INItemPlan.refNoteID, Equal<SOOrder.noteID>>,
InnerJoin<INPlanType, On<INItemPlan.FK.PlanType>>>>,
Where<INItemPlan.hold, Equal<boolFalse>,
And<INItemPlan.planQty, Greater<decimal0>,
And<INPlanType.isDemand, Equal<boolTrue>,
And<INPlanType.isForDate, Equal<boolTrue>,
And<Where<INItemPlan.fixedSource, IsNull, Or<INItemPlan.fixedSource, NotEqual<INReplenishmentSource.transfer>>>>>>>>>))]
public partial class SOShipmentPlan : IBqlTable { }
}
I'm trying to use a Selector to lookup up customers, and I want it to show the CD along with the description in the field. I've seen this many times in Acumatica - and I thought I knew how to do it, but it's not working. Here is my code:
#region CustomerLookup
public abstract class customerLookup : PX.Data.IBqlField
{
}
protected string _CustomerLookup;
[PXDBString(100, IsUnicode = true)]
[PXUIField(DisplayName = "Customer Lookup")]
[PXSelector(typeof(Customer.acctCD)
,typeof(Customer.acctCD)
,typeof(Customer.acctName)
,DescriptionField=typeof(Customer.acctName))]
public virtual string CustomerLookup
{
get
{
return this._CustomerLookup;
}
set
{
this._CustomerLookup = value;
}
}
#endregion
I would have thought providing the DescriptionField would take care of this, but it does not.
Before answering the question, let me first mention 2 major issues with the code snippet above:
In Acumatica Customers is one the specific system object types, that supports segmented keys. To create a look up for records, that support segmented keys, you should be using the PXDimensionSelectorAttribute instead of the PXSelectorAttribute. For Customer entity there are several attributes provided out of the box, like the CustomerAttribute or the CustomerActiveAttribute, that you can use to create Customer lookups:
CustomerLookup field must be of the Int32? (int?) type: in Acumatica users can change Customer ID with time (via the Change ID button on the Customers screen). To establish a foreign key for the Customer DAC, the better approach is to declare an integer field and associate your custom record with Customers though the Customer.BAccount field
Below is the recommended declaration of a DAC field, that creates Customer lookup in UI:
[Serializable]
public partial class DetailsResult : IBqlTable
{
...
#region CustomerId
public abstract class customerId : PX.Data.IBqlField
{
}
[Customer(DescriptionField = typeof(Customer.acctName))]
public virtual Int32? CustomerID { get; set; }
#endregion
...
}
With the DescriptionField property specified, PXDimensionSelector placed on a form will by default format selected record according to the "{SearchKey/SubstituteKey} - {DescriptionField}" pattern:
To show DescriptionField in a grid cell, you have to define additional column for the CustomerID_description field (where _description part is a special key word for Acumatica used specifically in this type of requests):
I believe the answer is one that's obvious - I don't think the description field works the same way in the grid. In a header field, it works to show the CD - Description when you navigate off of it. Grid fields apparently don't work that way, even when you specify the description field in the selector the way you would in a header selector.
Is there any way to extend/modify the projection query of a projected DAC.
For example, if I need to add a join statement to the projection and then use the newly joined table to the available fields.
Adding a custom field to the PXCacheExtension works as expected, but specifying the PXProjection query at the top of the PXCacheExtension DAC don't seems to have any effect.
Original:
[Serializable]
[PXProjection(typeof(Select5<EPApproval, InnerJoin<Note,
On<Note.noteID, Equal<EPApproval.refNoteID>,
And<EPApproval.status, Equal<EPApprovalStatus.pending>>>,>,
Where2<Where<EPApproval.ownerID, IsNotNull, And<EPApproval.ownerID, Equal<CurrentValue<AccessInfo.userID>>>>,
Or2<Where<EPApproval.workgroupID, InMember<CurrentValue<AccessInfo.userID>>,
Or<EPApproval.workgroupID, IsNull>>,
Or<EPApproval.workgroupID, Owned<CurrentValue<AccessInfo.userID>>>>>,
Aggregate<GroupBy<EPApproval.refNoteID,
GroupBy<EPApproval.curyInfoID,
GroupBy<EPApproval.bAccountID,
GroupBy<EPApproval.ownerID,
GroupBy<EPApproval.approvedByID,
GroupBy<EPApproval.curyTotalAmount>>>>>>>>))]
public partial class EPOwned : EPApproval{
Extended:
[Serializable]
[PXProjection(typeof(Select5<EPApproval, InnerJoin<Note,
On<Note.noteID, Equal<EPApproval.refNoteID>,
And<EPApproval.status, Equal<EPApprovalStatus.pending>>>,
LeftJoin<RQRequest, On<RQRequest.noteID, Equal<Note.noteID>>>>,
Where2<Where<EPApproval.ownerID, IsNotNull, And<EPApproval.ownerID, Equal<CurrentValue<AccessInfo.userID>>>>,
Or2<Where<EPApproval.workgroupID, InMember<CurrentValue<AccessInfo.userID>>,
Or<EPApproval.workgroupID, IsNull>>,
Or<EPApproval.workgroupID, Owned<CurrentValue<AccessInfo.userID>>>>>,
Aggregate<GroupBy<EPApproval.refNoteID,
GroupBy<EPApproval.curyInfoID,
GroupBy<EPApproval.bAccountID,
GroupBy<EPApproval.ownerID,
GroupBy<EPApproval.approvedByID,
GroupBy<EPApproval.curyTotalAmount>>>>>>>>))]
public class EPOwnedExt : PXCacheExtension<EPApprovalProcess.EPOwned> {
Thanks
To modify the projection query of a projected DAC, you should create an inherited DAC and decorate it with the PXSubstituteAttribute. Below is the sample for the FAAccrualTran DAC:
[Serializable]
[PXProjection(typeof(Select2<GLTran,
LeftJoin<FAAccrualTran, On<GLTran.tranID, Equal<FAAccrualTran.tranID>>>,
Where<GLTran.module, NotEqual<BatchModule.moduleFA>, And<GLTran.released, Equal<True>>>>), new Type[] { typeof(FAAccrualTran) })]
[PXSubstitute(GraphType = typeof(AssetGLTransactions))]
...
[PXSubstitute(GraphType = typeof(AssetMaint))]
public partial class FAAccrualTranCst : FAAccrualTran
{
...
}
You can decorate a DAC with PXSubstituteAttribute multiple times: 1 PXSubstituteAttribute per 1 BLC, for which custom FAAccrualTranCst will be used instead of the base FAAccrualTran class.
If you specify no value for GraphType property of the PXSubstituteAttribute, your custom DAC will replace its base DAC in all BLCs.