Acumatica - Possible to create several numberingId with existing numberingId - acumatica

i have some problems with AutoNumberAttribute in Acumatica. In my Project Issue screen i can create new entity with existing numberingId (see screenshot below)
Field selector with AutoNumberAttribute
But other entities like POOrder, Case haven't this promlems. Code for this shown below:
[Serializable]
[PXEMailSource]
[PXPrimaryGraph(typeof(ProjectIssueMaint))]
[PXCacheName(Messages.ProjectIssue.CacheName)]
public class ProjectIssue : BaseCache, IBqlTable, IAssign, IPXSelectable
{
[PXDBIdentity]
[PXUIField(Visible = false, Visibility = PXUIVisibility.Invisible, DisplayName = Messages.ProjectIssue.NumberId)]
public virtual int? ProjectIssueId
{
get;
set;
}
[PXDefault]
[PXFieldDescription]
[PXDBString(10, IsKey = true, IsUnicode = true, InputMask = ">CCCCCCCCCCCCCCC")]
[PXUIField(DisplayName = Messages.ProjectIssue.NumberId, Required = true)]
[PXSelector(typeof(Search<projectIssueCd>),
typeof(projectIssueCd),
typeof(projectId),
typeof(projectTaskId),
typeof(classId),
typeof(summary),
typeof(status),
typeof(ownerID),
Filterable = true)]
[AutoNumber(typeof(ProjectManagementSetup.projectIssueNumberingId), typeof(createdDateTime))]
public virtual string ProjectIssueCd
{
get;
set;
}
public abstract class projectIssueCd : IBqlField
{
}
public abstract class projectIssueId : IBqlField
{
}
}
[Serializable]
[PXCacheName(Messages.ProjectManagementSetup.CacheName)]
public class ProjectManagementSetup : BaseCache, IBqlTable
{
[PXDBString(10, IsUnicode = true, InputMask = ">aaaaaaaaaa")]
[PXDefault(Constants.ProjectIssue.NumberingId)]
[PXSelector(typeof(Numbering.numberingID), DescriptionField = typeof(Numbering.descr))]
[PXUIField(DisplayName = Messages.ProjectManagementSetup.ProjectIssueNumberingSequence)]
public virtual string ProjectIssueNumberingId
{
get;
set;
}
public abstract class projectIssueNumberingId : IBqlField
{
}
}
public class ProjectIssueMaint : PXGraph<ProjectIssueMaint, ProjectIssue>
{
[PXViewName(Messages.ProjectIssue.CacheName)]
[PXCopyPasteHiddenFields(typeof(ProjectIssue.status))]
public PXSelect<ProjectIssue> ProjectIssue;
[PXHidden]
[PXCheckCurrent]
public PXSetup<ProjectManagementSetup> ProjectManagementSetup;}
public class ProjectManagementSetupMaint : PXGraph<ProjectManagementSetupMaint>
{
public PXSave<ProjectManagementSetup> Save;
public PXCancel<ProjectManagementSetup> Cancel;
public PXSelect<ProjectManagementSetup> ProjectManagementSetup;
}
I reproproduce this issue by change the last number field in the numbering sequence screen (see screenshot https://snag.gy/UpIe8a.jpg).
So somebody know why this maybe happend? Any information will help me)

The AutoNumberAttribute completely relies on the configuration of the associated Numbering Sequence and does not have built-in validation to prevent insertion of the record with the duplicated number, which was generated by the AutoNumberAttribute. Since your table is built with A Pair of Columns with Key Substitution in the UI, you must add a unique index consisting of the CompanyID and ProjectIssueCd columns to prevent the insertion of multiple ProjectIssue records with the same ProjectIssueCd on the database level.
CREATE UNIQUE NONCLUSTERED INDEX [ProjectIssue_ProjectIssueCd_Uindex] ON [dbo].[ProjectIssue]
(
[CompanyID] ASC,
[ProjectIssueCd] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)

Related

Acumatica - An item with the same key has already been added

I have customization project in Acumatica 2021R1 (v21.104.0018). I have a parent DAC that has an ID and multiple child DACs that use this ID with a PXParent attribute. Whenever I select the screen in the customization project editor to design it, I get an error message that says, "An item with the same key has already been added." Then, I'm not able to design the screen in the Screen Editor. The weird thing is that I don't get this message when I open the actual screen.
I'm not sure what this is referring to because there are no records in the database in the tables that I'm using for the views of the graph for the screen.
Not sure if anyone else has had this problem and knows what causes it, but Acumatica could really use some more specific error messages.
Here is my parent DAC (note: I left out the system fields here for purposes of space):
public class PSImport : IBqlTable
{
#region Import ID
[PXDBString(15, IsKey = true, IsUnicode = true, InputMask = ">CCCCCCCCCCCCCCC")]
[PXDefault(PersistingCheck = PXPersistingCheck.NullOrBlank)]
[PXUIField(DisplayName = "Import No.", Visibility = PXUIVisibility.SelectorVisible, Enabled = true)]
[AutoNumber(typeof(PSSetup.numberingID), typeof(AccessInfo.businessDate))]
[PXSelector(
typeof(PSImport.importID),
typeof(PSImport.description),
typeof(PSImport.fileFormat),
typeof(PSImport.status),
typeof(PSImport.description)
)]
public virtual string ImportID { get; set; }
public abstract class importID : PX.Data.BQL.BqlString.Field<importID> { }
#endregion
#region Description
[PXDBString(IsUnicode = true, IsFixed = false)]
[PXUIField(DisplayName = "Description")]
public virtual string Description { get; set; }
public abstract class description : PX.Data.BQL.BqlString.Field<description> { }
#endregion
#region File Format
[PXDBString(2, IsFixed = true, IsUnicode = true)]
[PXUIField(DisplayName = "File Format", Enabled = true)]
[PXDefault]
[PXStringList(
new string[]
{
FileFormatTypes.Flats,
FileFormatTypes.IDOA,
FileFormatTypes.MMS,
FileFormatTypes.OCR,
FileFormatTypes.Pressero
},
new string[]
{
FileFormatTypeDisplayNames.Flats,
FileFormatTypeDisplayNames.IDOA,
FileFormatTypeDisplayNames.MMS,
FileFormatTypeDisplayNames.OCR,
FileFormatTypeDisplayNames.Pressero
}
)]
public virtual string FileFormat { get; set; }
public abstract class fileFormat : PX.Data.BQL.BqlString.Field<fileFormat> { }
#endregion
#region Status
[PXDBString(2, IsFixed = true, IsUnicode = true)]
[PXUIField(DisplayName = "Status", Enabled = false)]
[PXDefault(ImportStatusTypes.Unvalidated)]
[PXStringList(
new string[]
{
ImportStatusTypes.Unvalidated,
ImportStatusTypes.Invalid,
ImportStatusTypes.Valid,
ImportStatusTypes.Released
},
new string[]
{
ImportStatusTypeDisplayNames.Unvalidated,
ImportStatusTypeDisplayNames.Invalid,
ImportStatusTypeDisplayNames.Valid,
ImportStatusTypeDisplayNames.Released
}
)]
public virtual string Status { get; set; }
public abstract class status : PX.Data.BQL.BqlString.Field<status> { }
#endregion
#region Date Imported
[PXDBDate]
[PXUIField(DisplayName = "Date Imported", Enabled = false)]
public virtual DateTime? DateImported { get; set; }
public abstract class dateImported : PX.Data.BQL.BqlDateTime.Field<dateImported> { }
#endregion
#region Date of Last Validation
[PXDBDate]
[PXUIField(DisplayName = "Date of Last Validation", Enabled = false)]
public virtual DateTime? DateOfLastValidation { get; set; }
public abstract class dateOfLastValidation : PX.Data.BQL.BqlDateTime.Field<dateOfLastValidation> { }
#endregion
#region Date Released
[PXDBDate]
[PXUIField(DisplayName = "Date Released", Enabled = false)]
public virtual DateTime? DateReleased { get; set; }
public abstract class dateReleased : PX.Data.BQL.BqlDateTime.Field<dateReleased> { }
#endregion
}
Here is one of the child DACs (right now I only have the key because I don't know all of the fields that I need yet, and I'm just trying to start designing the screens):
public class PSTranFlats : IBqlTable
{
#region Import ID
[PXDBString(15, IsKey = true, IsUnicode = true, InputMask = "")]
[PXDBDefault(typeof(PSImport.importID))]
[PXParent(typeof(SelectFrom<PSImport>.
Where<PSImport.importID.IsEqual<PSTranFlats.importID.FromCurrent>>)
)]
public virtual string ImportID { get; set; }
public abstract class importID : PX.Data.BQL.BqlString.Field<importID> { }
#endregion
}
Here is my setup DAC:
[PXPrimaryGraph(typeof(PSSetupMaint))]
public class PSSetup : IBqlTable
{
#region NumberingID
[PXDBString(10, IsUnicode = true)]
[PXDefault("PSIMPORT")]
[PXUIField(DisplayName = "Import Numbering Sequence")]
[PXSelector(typeof(Numbering.numberingID), DescriptionField = typeof(Numbering.descr))]
public virtual string NumberingID { get; set; }
public abstract class numberingID : PX.Data.BQL.BqlString.Field<numberingID> { }
#endregion
}
Here is my setup graph:
public class PSSetupMaint : PXGraph<PSSetupMaint>
{
#region Selects / Views
public PXSave<PSSetup> Save;
public PXCancel<PSSetup> Cancel;
public SelectFrom<PSSetup>.View Setup;
#endregion
}
And here is my main graph:
public class PSImportEntry : PXGraph<PSImportEntry>
{
#region Selects / Views
public PXCancelClose<PSImport> CancelClose;
public PXSaveClose<PSImport> SaveClose;
public PXSave<PSImport> Save;
public PXCancel<PSImport> Cancel;
public PXInsert<PSImport> Insert;
public PXDelete<PSImport> Delete;
public PXFirst<PSImport> First;
public PXPrevious<PSImport> Previous;
public PXNext<PSImport> Next;
public PXLast<PSImport> Last;
public PXSetup<PSSetup> PSSetup;
public SelectFrom<PSImport>.View Import;
[PXImport(typeof(PSTranFlats))]
public SelectFrom<PSTranFlats>.
Where<PSTranFlats.importID.IsEqual<PSImport.importID.FromCurrent>>.View
FlatsTransactions;
[PXImport(typeof(PSTranIDOA))]
public SelectFrom<PSTranIDOA>.
Where<PSTranIDOA.importID.IsEqual<PSImport.importID.FromCurrent>>.View
IDOATransactions;
[PXImport(typeof(PSTranMMS))]
public SelectFrom<PSTranMMS>.
Where<PSTranMMS.importID.IsEqual<PSImport.importID.FromCurrent>>.View
MMSTransactions;
[PXImport(typeof(PSTranOCR))]
public SelectFrom<PSTranOCR>.
Where<PSTranOCR.importID.IsEqual<PSImport.importID.FromCurrent>>.View
OCRTransactions;
[PXImport(typeof(PSTranPressero))]
public SelectFrom<PSTranPressero>.
Where<PSTranPressero.importID.IsEqual<PSImport.importID.FromCurrent>>.View
PresseroTransactions;
#endregion
}
I am not 100% certain, but I think this is about the ID's in the ASPX rather than the database data. For example:
<px:PXLayoutRule LabelsWidth="S" ControlSize="SM" ID="PXLayoutRule1" runat="server" StartRow="True"></px:PXLayoutRule>
Notice ID="PXLayoutRule1" in that sample. If this "ID" value of any entry in the ASPX is duplicated anywhere in the screen as an ID of another entry, you will get an error.
I had similar issues in an earlier version, especially when I would try to customize a screen and have the customization screen interface crash on me in the middle of it. I'd try deleting the screen, but I'd get orphaned files in the CstDesigner and CstPublished folder, which would really compound my issues.
Hopefully, you know how to look through your ASPX file and the Customization Project XML to check all the ID's. If you do know how to walk through all that, just add something to all of your ID values, like an X at the end. (i.e. PXLayoutRule1X) If not, gaining that familiarity is a worthy side-objective as you continue on.
If your issue is the same as I had in the past and you cannot simply find and edit the duplicate ID in the ASPX file or Project XML, here are some steps to take AFTER you backup your project and the affected folders noted in these steps. And, of course, this also assumes you are working in a development copy of your instance. (I often clone my main DEV instance to my laptop to test things like this if I am unsure so that my DEV instance stays unaffected. It just takes extra time you may or may not have.)
Delete the screen from your customization project.
Locate the folder CstDesigner and CstPublished under your instance's root folder (i.e. AcumticaDEV, Sandbox, etc. where your site exists)
Go into the Pages_XX folder (where XX is the 2 character code for your 1st 2 letters of the screen ID)
Delete the screen file(s) from there, if you find them.
If this is a custom screen, go into the Pages folder which is located in the same root folder of your instance as those folders in step 2 and repeat steps 3 and 4 in that folder.
Check for any reference to your screen in the Project XML from the file menu in the Customization Project screen and remove those sections if they exist. (This tends to relate more for customizing an existing screen.)
Publish your project.
Start over on your page and see if you still get the error.
Again, be sure to get a backup before taking these steps so that you can easily put it back if that does not resolve your problem.

Acumatica APInvoice Extension Breaks Quick Checks

Version is 19.110.0013
I have created an inner join extension table for APInvoice. This has caused an issue with the Quick Checks flow. Specifically, creation of a Quick Check creates a record within the APInvoice table, but not a corresponding record in the extension table.
My DAC
using PX.Data;
using PX.Objects.AP;
namespace MyProject.DAC
{
[PXTable(IsOptional = false)]
[Serializable()]
public class ApInvoiceExtension : PXCacheExtension<APInvoice>
{
#region MyCustomFlag
public abstract class myCustomFlag : IBqlField { }
[PXDBBool()]
[PXUIField(DisplayName = "Custom Flag")]
public virtual bool? MyCustomFlag { get; set; }
#endregion
}
}
My Table
CREATE TABLE [dbo].[ApInvoiceExtension](
[CompanyID] [int] NOT NULL,
[DocType] [char](3) NOT NULL,
[RefNbr] [nvarchar](15) NOT NULL,
[MyCustomFlag] [bit] NULL,
CONSTRAINT [ApInvoiceExtension_PK] PRIMARY KEY CLUSTERED
(
[CompanyID] ASC,
[DocType] ASC,
[RefNbr] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[ApInvoiceExtension] ADD DEFAULT ((0)) FOR [CompanyID]
GO
I'm quite sure this has to do with the definition of APQuickCheck which is a DAC with the following attribute
[PXProjection(typeof(Select2<APRegister,
InnerJoin<APInvoice, On<APInvoice.docType, Equal<APRegister.docType>,
And<APInvoice.refNbr, Equal<APRegister.refNbr>>>,
InnerJoin<APPayment, On<APPayment.docType, Equal<APRegister.docType>,
And<APPayment.refNbr, Equal<APRegister.refNbr>>>>>,
Where<APRegister.docType, Equal<APDocType.quickCheck>,
Or<APRegister.docType, Equal<APDocType.voidQuickCheck>>>>), Persistent = true)]
The saving against this projection ignores the required extension records. I wondered if the issue was because the APInvoice referenced here was from the PX.Objects.AP.Standalone namespace instead of the PX.Objects.AP namespace which I extended, so I did attempt to get around this by creating a DAC extension for this namespace as well. My extension looked like this
using PX.Data;
using PX.Objects.AP.Standalone;
namespace MyProject.DAC.Standalone
{
[PXTable(IsOptional = false)]
[Serializable()]
public class ApInvoiceExtension : PXCacheExtension<APInvoice>
{
#region MyCustomFlag
public abstract class myCustomFlag : IBqlField { }
[PXDBBool()]
[PXUIField(DisplayName = "Custom Flag")]
public virtual bool? MyCustomFlag { get; set; }
#endregion
}
}
This addition didn't change the behavior, Quick Checks still created an entry in APInvoice but not in my APInvoiceExtension table.
You are absolutely right and this is because of PXProjection attribute. Please note that PXProjection is not a real table but like SQL view with a set of fields taken from several tables. You can see that APQuickCheck projection is a child APRegister class - as a result, it knows nothing about extended APInvoice fields and you should add them manually using PXDBFieldAttribute.BqlField property.
[PXNonInstantiatedExtension]
public class APQuickCheckExtension : PXCacheExtension<APQuickCheck>
{
#region MyCustomFlag
public abstract class myCustomFlag : IBqlField { }
[PXDBBool(BqlField = typeof(ApInvoiceExtension.myCustomFlag))]
[PXUIField(DisplayName = "Custom Flag")]
public virtual bool? MyCustomFlag
{
get;
set;
}
#endregion
}
[PXTable(IsOptional = false)]
[Serializable()]
public class ApInvoiceExtension : PXCacheExtension<APInvoice>
{
#region MyCustomFlag
public abstract class myCustomFlag : IBqlField { }
[PXDBBool()]
[PXUIField(DisplayName = "Custom Flag")]
public virtual bool? MyCustomFlag { get; set; }
#endregion
}
When doing an extension table you need to indicate the base table keys as the link in PXTable (or at least this was required back in 5.X when we first started using table extensions).
So your DAC extension might look like this:
[PXTable(typeof(APInvoice.docType), typeof(APInvoice.refNbr), IsOptional = false)]
[Serializable]
public class ApInvoiceExtension : PXCacheExtension<APInvoice>
{
#region MyCustomFlag
public abstract class myCustomFlag : IBqlField { }
[PXDBBool()]
[PXUIField(DisplayName = "Custom Flag")]
public virtual bool? MyCustomFlag { get; set; }
#endregion
}
Another note: Because your table is not optional you will need to pre-fill the extension table from the base table so you have all rows 1 for 1. I would recommend setting the IsOptional to true. When false and you have an APInvoice record without a matching APInvoiceExtension record the displayed values in screens and selectors might not display or function correctly.
Ended up opening a case with Acumatica. Response was that I needed to extend APRegister not APInvoice. Ended up changing APInvoice to APRegister (had to backfill my records) and it worked.
using PX.Data;
using PX.Objects.AP;
namespace MyProject.DAC
{
[PXTable(IsOptional = false)]
[Serializable()]
public class ApRegisterExtension : PXCacheExtension<ApRegister>
{
#region MyCustomFlag
public abstract class myCustomFlag : IBqlField { }
[PXDBBool()]
[PXUIField(DisplayName = "Custom Flag")]
public virtual bool? MyCustomFlag { get; set; }
#endregion
}
}
CREATE TABLE [dbo].[ApRegisterExtension](
[CompanyID] [int] NOT NULL,
[DocType] [char](3) NOT NULL,
[RefNbr] [nvarchar](15) NOT NULL,
[MyCustomFlag] [bit] NULL,
CONSTRAINT [ApRegisterExtension_PK] PRIMARY KEY CLUSTERED
(
[CompanyID] ASC,
[DocType] ASC,
[RefNbr] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[ApRegisterExtension] ADD DEFAULT ((0)) FOR [CompanyID]
GO

Need help in selector control

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:

Int32 cast error in Generic Inquiry after saving a row in the table

I created a new custom table, and a new screen (FormView) to enter data. I created a Generic Inquiry to navigate to the new entry screen. After I entered and save the first record in the new table with the new entry screen, the Generic Inquiry screen throws a casting error on one of the columns. This should all be very quick and easy, but I can't seem to get around the casting error.
New table structure:
CREATE TABLE [dbo].[DPItemRebate](
[CompanyID] [int] NOT NULL,
[VendorID] [int] NOT NULL,
[CustomerID] [int] NOT NULL,
[InventoryID] [int] NOT NULL,
[RebateAmt] [decimal](19, 4) NOT NULL,
CONSTRAINT [PK_DPItemRebate] PRIMARY KEY CLUSTERED
([CompanyID] ASC, [VendorID] ASC, [CustomerID] ASC, [InventoryID] ASC)...
DAC:
[Serializable]
public class DPItemRebate : IBqlTable
{
#region VendorID
[PXDBInt(IsKey = true)]
[PXUIField(DisplayName = "Vendor")]
[Vendor(typeof(Search<BAccountR.bAccountID,
Where<BAccountR.type, Equal<BAccountType.companyType>,
Or<Vendor.type, NotEqual<BAccountType.employeeType>>>>),
Visibility = PXUIVisibility.SelectorVisible, CacheGlobal = true,
Filterable = true)]
[PXRestrictor(typeof(Where<Vendor.status, IsNull,
Or<Vendor.status, Equal<BAccount.status.active>,
Or<Vendor.status, Equal<BAccount.status.oneTime>>>>),
PX.Objects.AP.Messages.VendorIsInStatus, typeof(Vendor.status))]
public virtual int? VendorID { get; set; }
public abstract class vendorID : IBqlField { }
#endregion
#region CustomerID
[PXDBInt(IsKey = true)]
[PXUIField(DisplayName = "Customer")]
[CustomerActive(typeof(Search<BAccountR.bAccountID,
Where<Customer.type, IsNotNull,
Or<BAccountR.type, Equal<BAccountType.companyType>>>>),
Visibility = PXUIVisibility.SelectorVisible,
DescriptionField = typeof(Customer.acctName),
Filterable = true)]
public virtual int? CustomerID { get; set; }
public abstract class customerID : IBqlField { }
#endregion
#region InventoryID
[PXDBInt(IsKey = true)]
[PXUIField(DisplayName = "Inventory Item")]
[Inventory(typeof(Search<InventoryItem.inventoryID,
Where2<Match<Current<AccessInfo.userName>>,
And<InventoryItem.itemStatus, NotEqual<InventoryItemStatus.unknown>>>>),
typeof(InventoryItem.inventoryCD), typeof(InventoryItem.descr),
Filterable = true)]
public virtual int? InventoryID { get; set; }
public abstract class inventoryID : IBqlField { }
#endregion
#region RebateAmt
[PXDBDecimal()]
[PXUIField(DisplayName = "Rebate Amount")]
public virtual Decimal? RebateAmt { get; set; }
public abstract class rebateAmt : IBqlField { }
#endregion
}
Generic Inquiry:
Tables: DPItemRebate.DPItemRebate
Relations: <none>
Results:
Object: DPItemRebate
Data Fields: vendorID, vendorID_description, customerID, customerID_description, inventoryID, inventoryID_description, rebateAmt
The DAC fields are lookups except for RebateAmt. I borrowed the selector attributes from elsewhere in Acumatica DAC code. I added all fields to a new entry page, entered data with the new page, then open the Generic Inquiry and it gives an error:
"[InvalidCastException: Specified cast is not valid.]
System.Data.SqlClient.SqlBuffer.get_Int32() +65
PX.Data.PXDataRecord.GetInt32(Int32 i) +96
PX.Data.PXDBIntAttribute.RowSelecting(PXCache sender, PXRowSelectingEventArgs e) +210
PX.Data.PXCache.OnRowSelecting(Object item, PXDataRecord record, Int32& position, Boolean isReadOnly) +650"
and
"[PXException: Error: An error occurred during processing of the field Customer: Specified cast is not valid..]"
When I comment out the DAC lookup definitions for Vendor, CustomerActive, And Inventory; the casting error goes away, so it's likely related; but I need these for field lookups. What am I defining wrong?
I changed all of my lookup field attribute definitions to base PXSelector attributes and it finally worked without error. I still don't know why the other Acumatica ones threw errors, though.
Do not redeclare [PXDBInt(IsKey = true)] it is already defined in the [CustomerActive] attribute.

Save Parent-Child

I am creating a graph using Acumatica Framework and I am trying to save changes to the database but I am getting an error on Save.
I have a typical parent/child (i.e. master/detail) relationships and everything should be set correctly.
My child DAC has the PXDBDefault and the PXParent entries:
[PXDBInt()]
[PXDBDefault(typeof(DCRuleHeader.ruleHeaderID))]
[PXParent(typeof(Select<DCRuleHeader, Where<DCRuleHeader.ruleHeaderID, Equal<Current<DCRule.ruleHeaderID>>>>))]
public virtual int? RuleHeaderID
{
get
{
return this._RuleHeaderID;
}
set
{
this._RuleHeaderID = value;
}
}
In the header, the ID is an identity in the database and defined as a key in the DAC as follows:
[PXDBIdentity(IsKey = true)]
[PXUIField(Enabled = false)]
public virtual int? RuleHeaderID
{
get
{
return this._RuleHeaderID;
}
set
{
this._RuleHeaderID = value;
}
}
The View is also configured accordingly:
public class RulesMaint : PXGraph<RulesMaint, DCRuleHeader>
{
public PXSelect<DCRuleHeader> RuleHeader;
public PXSelect<DCRule, Where<DCRule.ruleHeaderID, Equal<Current<DCRuleHeader.ruleHeaderID>>>> Rules;
public PXAction<DCRuleHeader> ViewRule;
However, the below code is not working
RulesMaint rulesGraph = PXGraph.CreateInstance<RulesMaint>();
DCRuleHeader newHeader = rulesGraph.RuleHeader.Insert();
...
DCRule rule = rulesGraph.Rules.Insert();
...
rulesGraph.Actions.PressSave();
When I try the above, I get the error 'Error "RuleHeaderID" cannot be empty'
Shouldn't the framework handle everything itself and set the Parent ID of the child object automatically? Isn't that what PXDBDefault is for ?
Here is example how you can get Parent/Child DACs working with Form Detail view in Acumatica ERP.
For the begging let's create SQL Tables for Parent and Child in the following way:
Parent:
/****** Object: Table [dbo].[SOCustomParentTable] Script Date: 07/03/2017 12:55:17 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[SOCustomParentTable](
[CompanyID] [int] NOT NULL,
[ParentID] [int] IDENTITY(1,1) NOT NULL,
[Description] [nvarchar](255) NULL,
[SomeOtherField] [nvarchar](50) NULL,
[ParentCD] [nvarchar](15) NOT NULL
) ON [PRIMARY]
GO
And the child
/****** Object: Table [dbo].[SOCustomChildTable] Script Date: 07/03/2017 12:54:39 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[SOCustomChildTable](
[CompanyID] [int] NOT NULL,
[ChildID] [int] IDENTITY(1,1) NOT NULL,
[ParentID] [int] NOT NULL,
[Description] [nvarchar](255) NULL,
[SomeOtherField] [nvarchar](50) NULL
) ON [PRIMARY]
GO
Now as we have SQL Tables ready let's create DAC's as the following classes:
Parent :
using System;
using PX.Data;
namespace DemoParentChild
{
[Serializable]
public class SOCustomParentTable: IBqlTable
{
#region ParentID
[PXDBIdentity()]
public int? ParentID { get; set; }
public class parentID : IBqlField{}
#endregion
#region Description
[PXDBString(255, IsUnicode = true, InputMask = "")]
[PXUIField(DisplayName = "Description")]
public string Description { get; set; }
public class description : IBqlField{}
#endregion
#region SomeOtherField
[PXDBString(50, IsUnicode = true, InputMask = "")]
[PXUIField(DisplayName = "Some Other Field")]
public string SomeOtherField { get; set; }
public class someOtherField : IBqlField{}
#endregion
#region ParentCD
[PXDBString(15,IsKey = true, IsUnicode = true, InputMask = "")]
[PXUIField(DisplayName = "Parent ID")]
public string ParentCD { get; set; }
public class parentCD : IBqlField{}
#endregion
}
}
And the child:
using System;
using PX.Data;
namespace DemoParentChild
{
[Serializable]
public class SOCustomChildTable: IBqlTable
{
#region ChildID
[PXDBIdentity(IsKey=true)]
public int? ChildID { get; set; }
public class childID : IBqlField{}
#endregion
#region ParentID
[PXDBInt()]
[PXDBDefault(typeof(SOCustomParentTable.parentID))]
[PXParent(typeof(Select<SOCustomParentTable, Where<SOCustomParentTable.parentID, Equal<Current<SOCustomChildTable.parentID>>>>))]
public int? ParentID { get; set; }
public class parentID : IBqlField{}
#endregion
#region Description
[PXDBString(255, IsUnicode = true, InputMask = "")]
[PXUIField(DisplayName = "Description")]
public string Description { get; set; }
public class description : IBqlField{}
#endregion
#region SomeOtherField
[PXDBString(50, IsUnicode = true, InputMask = "")]
[PXUIField(DisplayName = "Some Other Field")]
public string SomeOtherField { get; set; }
public class someOtherField : IBqlField{}
#endregion
}
}
And to finish our work let's create a page of FormDetail type with the following Graph:
using System;
using PX.Data;
namespace DemoParentChild
{
public class tmp : PXGraph<tmp>
{
public PXSave<SOCustomParentTable> Save;
public PXCancel<SOCustomParentTable> Cancel;
public PXPrevious<SOCustomParentTable> Prev;
public PXNext<SOCustomParentTable> Next;
public PXSelect<SOCustomParentTable> MasterView;
public PXSelect<SOCustomChildTable,Where<SOCustomChildTable.parentID,Equal<Current<SOCustomParentTable.parentID>>>> DetailsView;
}
}
Now let's understand how this all is working.
As you can see the ParentID is Identity in SQL and is set to PXDBIdentity in DAC, but it's not set to be a Key for our DAC as we will use ParentCD as visible Key.
Also in the Child class the ChildID is set to PXDBIdentity, but it is set to be Key also as we don't need the line to have visible key for user.
Also in the Child class we have the following for creation of the Parent/Child relation:
[PXDBInt()]
[PXDBDefault(typeof(SOCustomParentTable.parentID))]
[PXParent(typeof(Select<SOCustomParentTable, Where<SOCustomParentTable.parentID, Equal<Current<SOCustomChildTable.parentID>>>>))]
public int? ParentID { get; set; }
Where PXDefault is setting ParendID of the Child to current Parent's ID and PXParent is creating the Relation between current Child and the Parent.
Commonly there are being created Line Number for child and Line Counter on the Parent to know the count of the Childs.
The full customization you can download here
I was having trouble with this before, because i have a habit of putting new code at the top of the editor, turns out that the data view selection (child) should always be at the bottom (after pxselect)

Resources