How to Override the InventoryCD Number Sequence - acumatica

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.

Related

How to change the display name of description field of selector

I have a customization where I'm adding a Selector to a grid field that has a description field as follows:
[PXSelector(typeof(Search<EPEmployee.bAccountID,
Where<EPEmployee.status, Equal<SetupTypes.active>>>),
typeof(EPEmployee.acctCD),
typeof(EPEmployee.acctName),
SubstituteKey = typeof(EPEmployee.acctCD),
DescriptionField = typeof(EPEmployee.acctName))]
My problem is that the field has a description (in this case, 'Employee Name') field that is automatically added, but I can't find a way to change the display name of that field.
Since I have another field that uses the same employee lookup, they both have the same 'Employee Name' description field - so I have no way of knowing which description goes with which Selector field unless I choose a value and see the description show up in its associated 'Employee Name' field.
Is there a way to change the display name of that description field? I've tried CacheAttached and the RowSelected event with a PXUIFieldAttribute.SetDisplayName, but nothing seems to work.. seems that 'field_description' is an automatically added field that doesn't exist anywhere in the DAC to be able to change the display name.
Per Acumatica support, the only way that this can be done is as follows:
The only way that seems to work is to have two different DACs that are projections on the EPEmployee DAC and use each in the separate selector attribute.
For instance:
[PXProjection(typeof(EPEmployee))]
public class EPEmployeeTest : IBqlTable
{
#region BAccountID
...
#endregion
#region AcctCD
...
#endregion
public abstract class acctName : PX.Data.BQL.BqlString.Field<acctName> { }
[PXDBString(60, IsUnicode = true, BqlField=typeof(EPEmployee.acctName))]
[PXUIField(DisplayName = "Employee Name New Label", Enabled = false, Visibility = PXUIVisibility.SelectorVisible)]
public override string AcctName{get; set;}
}

PXFormula not updating

I've created a new custom bound field UsrMatchCode for the BAccount DAC. My goal is to create a field which I can query from a web service call to identify "potential" duplicate customer records. My plan is to concatenate the following into a single 9-character calculated field:
First 5 Characters of Address.PostalCode
First 2 Characters of Address.AddressLine1
First 2 Characters of Contact.FullName
But before I can get this far, I'm stuck getting the value of the PXFormula to display when updating/saving existing records. Strangely, it does output a value for new records. See screenshot.
I've reduced the PXFormula to using constants only to rule out other fields causing the problem.
public class BAccountExt : PXCacheExtension<PX.Objects.CR.BAccount>
{
private class index1 : Constant<int>
{
public index1() : base(0) { }
}
private class length5 : Constant<int>
{
public length5() : base(5) { }
}
private class testString : Constant<string>
{
public testString() : base("123456789") { }
}
#region UsrMatchCode
public abstract class usrMatchCode : IBqlField { }
[PXDBString(9)]
[PXUIField(DisplayName = "Match Code", Enabled = false)]
[PXFormula(typeof(Substring<testString, index1, length5>))]
public virtual string UsrMatchCode { get; set; }
#endregion
}
I feel like I'm missing something obvious, but I can't work it out.
I avoid using PXFormula and PXDBxxx types at the same time.
The PXDBxxx attribute tells the framework you want to persist the field value to DB and reload it's value from DB while PXFormula tells the framework the string value needs to be computed from the formula. The two approach appear incompatible because it's not clear cut whether the value will be coming from DB or from formula after the record has been persisted to DB. Other attributes like PXDefault are more suited for PXDBxxx types because it will only run at initialisation.
I would recommend either to use PXFormula with the unbound PXString type or to use PXDefault instead of PXFormula for initialisation of PXDBString.

Adding custom selector to a grid

I am having an issue with adding a selector for a custom field to a grid. I am having a lot of issues and I am a bit lost.
We need to add a selector to the "Documents to Apply" tab on the AR302000 screen (Finance -> Accounts Recievable -> Payments and Applications). This selector would be based off of a custom field we added to ARRegister.
public abstract class usrUploadDocNbr : IBqlField { }
[PXDBString(15)]
[PXUIField(DisplayName = Messages.UploadDocNbr)]
public virtual string UsrUploadDocNbr { get; set; }
I did not decorate this with the PXSelector tag since we use this field as with a textbox most of the time. From there, I added it to the AR302000 grid mentioned above, but the field was always disabled.When I looked at the fields available to add to the grid, there is ARInvoice__UsrUploadDocNbr and ARRegisterAlias__UsrUploadDocNbr. This kind of makes sense to me,but only a little bit.
When I tried to use either one of those fields, the row in the grid would always be read-only. From here, I figured I would try to add the field to ARAdj since this was the type of many of the fields in the grid. I added the following code:
public abstract class usrUploadDocNbr : IBqlField, IBqlOperand { }
[PXDBString(BqlField =typeof(ArRegisterExt.usrUploadDocNbr))]
[PXUIField(DisplayName = Messages.UploadBatchNbr, Enabled = true)]
[PXSelector(typeof(ARRegister.refNbr),
typeof(ARAdjust.ARInvoice.refNbr),
typeof(ARAdjust.ARInvoice.docDate),
typeof(ArRegisterExt.usrUploadDocNbr),
typeof(ARAdjust.ARInvoice.finPeriodID),
typeof(ARAdjust.ARInvoice.customerID),
typeof(ARRegister.customerLocationID),
typeof(PX.Objects.AR.Standalone.ARRegister.curyID),
typeof(ARRegister.curyOrigDocAmt),
typeof(ARRegister.curyDocBal),
typeof(PX.Objects.AR.Standalone.ARRegister.status),
typeof(ARAdjust.ARInvoice.dueDate),
typeof(ARAdjust.ARInvoice.invoiceNbr),
typeof(PX.Objects.AR.Standalone.ARRegister.docDesc),
SubstituteKey = typeof(ArRegisterExt.usrUploadDocNbr))]
public virtual string UsrUploadDocNbr { get; set; }
When I added this field, the selector worked, sort of. I was able to have the selector window open, but all the column names were the type name rather than the annotated name (as an example, usrUploadDocNbr rather than "Upload Doc. Nbr.").
But another issue popped up - the Refernce Nbr selector no longer works. We added the UsrUploadDocNbr to the Reference Nbr selector. The code is as follows:
[PXMergeAttributes(Method = MergeMethod.Append)]
[PXCustomizeSelectorColumns(
typeof(ARAdjust.ARInvoice.refNbr),
typeof(ARAdjust.ARInvoice.docDate),
typeof(ArRegisterExt.usrUploadDocNbr),
typeof(ARAdjust.ARInvoice.finPeriodID),
typeof(ARAdjust.ARInvoice.customerID),
typeof(ARRegister.customerLocationID),
typeof(PX.Objects.AR.Standalone.ARRegister.curyID),
typeof(ARRegister.curyOrigDocAmt),
typeof(ARRegister.curyDocBal),
typeof(PX.Objects.AR.Standalone.ARRegister.status),
typeof(ARAdjust.ARInvoice.dueDate),
typeof(ARAdjust.ARInvoice.invoiceNbr),
typeof(PX.Objects.AR.Standalone.ARRegister.docDesc))]
public virtual string AdjdRefNbr { get; set; }
The error that pops up reads (it does display the same thing twice in a single modal):
Invalid column name 'UsrUploadDocNbr'
Invalid column name 'UsrUploadDocNbr'
And at this point I am pretty lost. I am sure I am doing many things wrong, just not sure what it is.
First:
You should give typeof(Search<DAC.FIELD>) to the PXSelector as first parameter.
So change this line
[PXSelector(typeof(ARRegister.refNbr),
To
[PXSelector(typeof(Search<ARRegister.refNbr>),
Second:
Message
Invalid column name 'UsrUploadDocNbr'
mostly is being shown(if the field is decorated with PXDB... like attribute) when you have not created the field in the Database's Table.

Filtering PXselector with search

I have a custom grid using a custom dac that I have declared. Originally i had a PXselector attribute set to POOrder.orderNbr
With this selector it grabs all POOrders in my grid selector
[PXDBString(50, IsKey = true, IsUnicode = true, InputMask = "")]
[PXSelector(typeof(POOrder.orderNbr))]
[PXUIField(DisplayName = "Po#")]
public string Po { get; set; }
public class po : IBqlField { }
But i want the selector to filter POOrders that appear on the Document details->POReceiptLine. I attempted to filter using the Search but only get the lowest value POOrder.ordnbr on the receipt. The images below should illustrate what I mean. I want it to show all POOrder.orderNbr but it's only retrieving the first lowest value order.
[PXDBString(50, IsKey = true, IsUnicode = true, InputMask = "")]
[PXSelector(typeof(Search<POOrder.orderNbr,Where<POOrder.orderNbr,Equal<Current2<POReceiptLine.pONbr>>>>))]
[PXUIField(DisplayName = "Po#")]
public string Po { get; set; }
public class po : IBqlField { }
I believe it would be much better if you inner join POReceiptLine DAC, and then aggregate associated Purchase Orders to eliminate possible duplicates:
[PXSelector(typeof(Search5<POOrder.orderNbr,
InnerJoin<POReceiptLine, On<POReceiptLine.pOType, Equal<POOrder.orderType>,
And<POReceiptLine.pONbr, Equal<POOrder.orderNbr>,
And<POReceiptLine.receiptNbr, Equal<Current<POReceipt.receiptNbr>>>>>>,
Aggregate<GroupBy<POOrder.orderType,
GroupBy<POOrder.orderNbr>>>>))]
The main issue in the selector is that you are looking for POOrder where the order Number equals the current Receipt Line. That means that depending the line selected, the value will change but will always yield only one result. The solution would then be to change the condition to be on the Purchase Receipt document instead.
To do so you have two options. Either you search directly POReceiptLine.poNbr, but then your selector will display columns from that table, or you join POOrder to POReceiptLine.
With search on POReceiptLine
[PXSelector(typeof(Search<POReceiptLine.pONbr,
Where<POReceiptLine.receiptType,
Equal<Optional<POReceipt.receiptType>>,
And<POReceiptLine.receiptNbr,
Equal<Optional<POReceipt.receiptNbr>>>>>), DirtyRead = true)]
With search on POOrder
[PXSelector(typeof(Search2<POOrder.orderNbr,
InnerJoin<POReceiptLine, On<POReceiptLine.pOType,
Equal<POOrder.orderType>,
And<POReceiptLine.pONbr,
Equal<POOrder.orderNbr>>>>,
Where<POReceiptLine.receiptType,
Equal<Optional<POReceipt.receiptType>>,
And<POReceiptLine.receiptNbr,
Equal<Optional<POReceipt.receiptNbr>>>>>))]
Also notice the parameter DirtyRead = true. This parameter will tell the selector to get it's information in the cache and not only from the Database. Because you are selecting something from the same page, you would need to save the whole document before being able to select the record otherwise. Unfortunatly it would not work with the second option because the Join is done at the database level.

Get combination of CD and description for PXSelector

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.

Resources