How To Manage Default Order Of Columns In Selector Of Acumatica

How to manage default order of Columns in selector of Acumatica

Hello everybody,

today I want to leave a short notice on how to manage order of columns in Selector of Acumatica. Quite often customers has desire to manage default order of columns in selector. That is very easy to achieve with new Type[] parameter added to your selector. Take a look on how I've managed list of columns for Carrier with help of new Type[]:

[PXDBString(15, InputMask = ">aaaaaaaaaaaaaaa", IsKey = true, IsUnicode = true)]
[PXDefault]
[PXUIField(DisplayName = "Ship Via", Visibility = PXUIVisibility.SelectorVisible)]
[PXSelector(typeof(Search<Carrier.carrierID>), new Type[]
       {
         typeof(Carrier.description), typeof(CarrierExt.usrDeliveryLength), typeof(Carrier.confirmationRequired),
         typeof(Carrier.packageRequired), typeof(Carrier.isCommonCarrier)
       },
        CacheGlobal = true, DescriptionField = typeof(Carrier.description))]
protected void Carrier_CarrierID_CacheAttached(PXCache sender)
       {
       }
    }

And below goes screenshot of selector:

As you can see at screenshot, column Delivery duration appeared second in the list, but by default, if I've added that column later, it would be displayed in the end of list.

No Comments

Add a Comment

PXDimensionselector And PXSelector In Acumatica

 

Hello everybody,

Today I want to describe PXDimensionSelector attribute usage in Acumatica. According to manual PXDimensionSelector attribute has following purpose:

Defines an input control that combines the functionality of the PXDimenstion attribute and the PXSelector attribute. A user can view the data set defined by the attribute and select a data record from this data set to assign its segmented key value to the field or to replace it with the surrogate key

After reading such purpose, I've decided to read purpose of PXDimension attribute:

Defines an input control that formats the input as a segmented key value and displays the list of allowed values for each key segment.

If those two descriptionss made you understand their pupose and way of usage, then congratulations, but for me it took muuuuuuuch longer. 

Business case ( some details intentionally changed )

I was asked to create table, which has some information about Accounts and their belonging to branches ( later I'll provide sql for table creation as well as filling of created table it with some data ). Then page Sales orders has column Branch, Account, SubAccount should filter list of Accounts and SubAccounts as you can see on the screenshot:

requirement is the following: values at column Account should be filtered by what is selected in the Column Branch by means of showing only those Accounts, which has the same branch in table CompanyAccount.

Preparation

In order to give you a better idea how data was looking like, I've created some sample data with DAC class. Also in order to have some data to play around, I've installed Acumatica with SalesDemo data in order to avoid headache of configuring all from scratch.

Below goes SQL for table creation and filling with some data.

Table creation sql:

SET ANSI_NULLS ON
GO
 
SET QUOTED_IDENTIFIER ON
GO
 
CREATE TABLE [dbo].[CompanyAccount](
	[CompanyID] [int] NOT NULL,
	[CmpBranchID] [int] NOT NULL,
	[AccountID] [int] NOT NULL,
	[Description] [nvarchar](60) NULL,
	[tstamp] [timestamp] NOT NULL,
	[CreatedByID] [uniqueidentifier] NOT NULL,
	[CreatedByScreenID] [char](8) NOT NULL,
	[CreatedDateTime] [datetime] NOT NULL,
	[LastModifiedByID] [uniqueidentifier] NOT NULL,
	[LastModifiedByScreenID] [char](8) NOT NULL,
	[LastModifiedDateTime] [datetime] NOT NULL,
	[NoteID] [uniqueidentifier] NULL,
 CONSTRAINT [PK_CompanyAccount] PRIMARY KEY CLUSTERED 
(
	[CompanyID] ASC,
	[CmpBranchID] ASC,
	[AccountID] 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].[CompanyAccount] ADD  CONSTRAINT [DF_CompanyAccount_CompanyID]  DEFAULT ((0)) FOR [CompanyID]
GO
 
ALTER TABLE [dbo].[CompanyAccount] ADD  CONSTRAINT [DF_Table_1_CNBranchID]  DEFAULT ((0)) FOR [CmpBranchID]
GO

Filling with data of CompanyAccount table:

INSERT INTO [dbo].[CompanyAccount] ([CompanyID],[CmpBranchID],[AccountID],[Description],[CreatedByID],[CreatedByScreenID],[CreatedDateTime]
		,[LastModifiedByID],[LastModifiedByScreenID],[LastModifiedDateTime],[NoteID])
     VALUES (
				2, 16,1195, 'Discount Taken', 'B5344897-037E-4D58-B5C3-1BDFD0F47BF9', 'AR301000', '2018-12-13 00:52:31.550', 
				'B5344897-037E-4D58-B5C3-1BDFD0F47BF9', 'AR301000', '2018-12-13 00:52:31.550', NULL)
 
INSERT INTO [dbo].[CompanyAccount] ([CompanyID],[CmpBranchID],[AccountID],[Description],[CreatedByID],[CreatedByScreenID],[CreatedDateTime]
		,[LastModifiedByID],[LastModifiedByScreenID],[LastModifiedDateTime],[NoteID])
     VALUES
           (
				2,16,1223,'Office Expense', 'B5344897-037E-4D58-B5C3-1BDFD0F47BF9', 'AR301000', '2018-12-13 00:52:31.550', 
				'B5344897-037E-4D58-B5C3-1BDFD0F47BF9', 'AR301000', '2018-12-13 00:52:31.550', NULL
		   )
INSERT INTO [dbo].[CompanyAccount] ([CompanyID],[CmpBranchID],[AccountID],[Description],[CreatedByID],[CreatedByScreenID],[CreatedDateTime]
		,[LastModifiedByID],[LastModifiedByScreenID],[LastModifiedDateTime],[NoteID])
     VALUES
           (
				2,16,1224,'Postage and Delivery', 'B5344897-037E-4D58-B5C3-1BDFD0F47BF9', 'AR301000', '2018-12-13 00:52:31.550', 
				'B5344897-037E-4D58-B5C3-1BDFD0F47BF9', 'AR301000', '2018-12-13 00:52:31.550', NULL
		   )
INSERT INTO [dbo].[CompanyAccount] ([CompanyID],[CmpBranchID],[AccountID],[Description],[CreatedByID],[CreatedByScreenID],[CreatedDateTime]
		,[LastModifiedByID],[LastModifiedByScreenID],[LastModifiedDateTime],[NoteID])
     VALUES
           (
				2,21,1166,'Computer & Office Equipment', 'B5344897-037E-4D58-B5C3-1BDFD0F47BF9', 'AR301000', '2018-12-13 00:52:31.550', 
				'B5344897-037E-4D58-B5C3-1BDFD0F47BF9', 'AR301000', '2018-12-13 00:52:31.550', NULL
		   )
INSERT INTO [dbo].[CompanyAccount] ([CompanyID],[CmpBranchID],[AccountID],[Description],[CreatedByID],[CreatedByScreenID],[CreatedDateTime]
		,[LastModifiedByID],[LastModifiedByScreenID],[LastModifiedDateTime],[NoteID])
     VALUES
           (
				2,21,1167,'Machinery & Equipment', 'B5344897-037E-4D58-B5C3-1BDFD0F47BF9', 'AR301000', '2018-12-13 00:52:31.550', 
				'B5344897-037E-4D58-B5C3-1BDFD0F47BF9', 'AR301000', '2018-12-13 00:52:31.550', NULL
		   )

As you can see from the sql, Branch with id 16

DAC class for CompanyTable:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using PX.Data;
using PX.Objects.GL;
 
namespace AcumaticaSamples
{
    [Serializable]
    public class CompanyAccount : IBqlTable
    {
        #region NoteID
 
        public abstract class noteID : PX.Data.IBqlField
        {
        }
 
        [PXNote]
        public virtual Guid? NoteID { getset; }
 
        #endregion
 
        #region BranchID
        public abstract class cmpBranchID : IBqlField
        {
        }
 
        [PXDBInt(IsKey = true)]
        [PXDefault]
        [PXUIField(DisplayName = "Branch ID")]
        public virtual int? CmpBranchID { getset; }
 
        #endregion
 
        #region AccountID
        public abstract class accountID : IBqlField
        {
        }
 
        [PXDBInt(IsKey = true)]
        [PXDefault]
        [PXUIField(DisplayName = "Account", IsReadOnly = true)]
        [PXSelector(typeof(Search<Account.accountID>), SubstituteKey = typeof(Account.accountCD))]
        public virtual int? AccountID { getset; }
 
        #endregion
 
 
        #region Description
        public abstract class description : IBqlField
        {
        }
        [PXDBString(60)]
        [PXUIField(DisplayName = "Description")]
        public virtual string Description { getset; }
        #endregion
        #region CreatedByID
        public abstract class createdByID : IBqlField
        {
        }
        protected Guid? _CreatedByID;
        [PXDBCreatedByID()]
        public virtual Guid? CreatedByID
        {
            get
            {
                return this._CreatedByID;
            }
            set
            {
                this._CreatedByID = value;
            }
        }
        #endregion
        #region CreatedByScreenID
        public abstract class createdByScreenID : PX.Data.IBqlField
        {
        }
        protected String _CreatedByScreenID;
        [PXDBCreatedByScreenID()]
        public virtual String CreatedByScreenID
        {
            get
            {
                return this._CreatedByScreenID;
            }
            set
            {
                this._CreatedByScreenID = value;
            }
        }
        #endregion
        #region CreatedDateTime
        public abstract class createdDateTime : IBqlField
        {
        }
        protected DateTime? _CreatedDateTime;
        [PXDBCreatedDateTime()]
        public virtual DateTime? CreatedDateTime
        {
            get
            {
                return this._CreatedDateTime;
            }
            set
            {
                this._CreatedDateTime = value;
            }
        }
        #endregion
        #region LastModifiedByID
        public abstract class lastModifiedByID : IBqlField
        {
        }
        protected Guid? _LastModifiedByID;
        [PXDBLastModifiedByID()]
        public virtual Guid? LastModifiedByID
        {
            get
            {
                return this._LastModifiedByID;
            }
            set
            {
                this._LastModifiedByID = value;
            }
        }
        #endregion
        #region LastModifiedByScreenID
        public abstract class lastModifiedByScreenID : PX.Data.IBqlField
        {
        }
        protected String _LastModifiedByScreenID;
        [PXDBLastModifiedByScreenID()]
        public virtual String LastModifiedByScreenID
        {
            get
            {
                return this._LastModifiedByScreenID;
            }
            set
            {
                this._LastModifiedByScreenID = value;
            }
        }
        #endregion
        #region LastModifiedDateTime
        public abstract class lastModifiedDateTime : IBqlField
        {
        }
        protected DateTime? _LastModifiedDateTime;
        [PXDBLastModifiedDateTime()]
        public virtual DateTime? LastModifiedDateTime
        {
            get
            {
                return this._LastModifiedDateTime;
            }
            set
            {
                this._LastModifiedDateTime = value;
            }
        }
        #endregion
        #region tstamp
        public abstract class Tstamp : PX.Data.IBqlField
        {
        }
        protected Byte[] _tstamp;
        [PXDBTimestamp]
        public virtual Byte[] tstamp
        {
            get
            {
                return this._tstamp;
            }
            set
            {
                this._tstamp = value;
            }
        }
        #endregion
    }
}

Limit Accounts

When I was asked to limit Accounts selector in Sales orders, I thought it would be easy task, so I went to definition of SOLine, and found the following C# code:

    [PXDefault(PersistingCheck = PXPersistingCheck.Nothing)]
    [Account(typeof (SOLine.branchID), Visible = false)]
    public virtual int? SalesAcctID
    {
      get
      {
        return this._SalesAcctID;
      }
      set
      {
        this._SalesAcctID = value;
      }
    }

Every time I see custom attributes of Acumatica during development I have some thought in background like ups, surprises will come for sure.

And they come. But let's try to make substitution for SalesAcctID with help of ordinary PXSelector in SOORderEntryExt:

public class SOOrderEntryExt : PXGraphExtension<SOOrderEntry>
    {
        [PXDefault(PersistingCheck = PXPersistingCheck.Nothing)]
        [PXDBInt]
        [PXSelector(typeof(Search2<Account.accountID, 
            InnerJoin<CompanyAccountOn<Account.accountIDEqual<CompanyAccount.accountID>>>, 
                Where<CompanyAccount.cmpBranchIDEqual<Current<SOLine.branchID>>>>),
            typeof(Account.accountID), typeof(Account.accountCD), typeof(Account.description))]
        [PXUIField(DisplayName = "Account CD")]
        protected void SOLine_SalesAcctID_CacheAttached(PXCache sender)
        {
        }
    }

nothing fancy. Take a look at final result:

as you can see from screen recording, filtering works wonderful, but after selection of Account, field remains empty. How to deal with that? In that case on rescue comes PXDimensionSelector!

I will tell you mine own definition of PXDimensionSelector: Attribute, that allows you to modify any custom Acumatica selector. For example you can use PXDimensionSelector for filtering Accounts, SubAccounts ( but for that you'll need to play with  Segmented keys), etc.

Take a look on the code, which replaces PXSelector with PXDimensionSelector:

public class SOOrderEntryExt : PXGraphExtension<SOOrderEntry>
    {
        [PXUIField(DisplayName = "Account", FieldClass = "ACCOUNT", Visibility = PXUIVisibility.Visible)]
        [PXDBInt]
        [PXDimensionSelector(AccountAttribute.DimensionName,
            typeof(Search2<Account.accountIDInnerJoin<CompanyAccountOn<Account.accountIDEqual<CompanyAccount.accountID>>>,
                Where<CompanyAccount.cmpBranchIDEqual<Current<SOLine.branchID>>>>), typeof(Account.accountCD),
            new Type[] { typeof(Account.accountCD), typeof(CompanyAccount.description) })]
        protected void SOLine_SalesAcctID_CacheAttached(PXCache sender)
        {
        }
    }
And final result:

As you can see from the screen recording, PXDimensionSelector allows you to filter account as well as select, while PXSelector only filters accounts, but doesn't allow you to select proper account

Filters In Acumatica

Filters in Acumatica

Hello everybody,

in this article I want to describe how filtering in Acumatica work. When I say filtering, I mean following UI part:

In manuals T100 - T300 there is almost nothing about how filters work, so I want to share few bits of development information.

Storage

All information about filters is stored in database in tables FilterRow and FilterHeader.

Take a look on screenshot of FilterInformation for some custom applied filter:

 

As you can see from screenshot Acumatica stores information about filters by FilterID, and has information like Condition, values, Close brackets, operators and so on.

Also there are DAC classes FilterRow and FilterHeader, but you can find them only with help of reflector or any other "spy tool".

Modification

Acumatica allows you to work with filters with help of Search functionality in selector and similar to it ( for example Search2 ). Take a look on declaration of filter of APPayment column RefNbr:

        [PXDBString(15, InputMask = ">CCCCCCCCCCCCCCC", IsKey = true, IsUnicode = true)]
        [PXDefault]
        [PXUIField(DisplayName = "Reference Nbr.", TabOrder = 1, Visibility = PXUIVisibility.SelectorVisible)]
        [APInvoiceType.RefNbr(typeof(Search2<APRegisterAlias.refNbrInnerJoinSingleTable<APInvoiceOn<APInvoice.docType, 
            Equal<APRegisterAlias.docType>, And<APInvoice.refNbrEqual<APRegisterAlias.refNbr>>>, 
            InnerJoinSingleTable<VendorOn<APRegisterAlias.vendorIDEqual<Vendor.bAccountID>>>>, 
            Where<APRegisterAlias.docTypeEqual<Optional<APInvoice.docType>>, 
                And2<Where<APRegisterAlias.origModuleNotEqual<BatchModule.moduleTX>, Or<APRegisterAlias.released, 
                    Equal<True>>>, And<Match<VendorCurrent<AccessInfo.userName>>>>>, OrderBy<Desc<APRegisterAlias.refNbr>>>), 
            Filterable = true, IsPrimaryViewCompatible = true)]
        [APPaymentType.Numbering]
        [PXFieldDescription]

The same filtering is applied at APInvoice. And now you can ask, ok, so what it gives me? Actually it means that by default, if you make some filter as default for screen of invoices, then the same filter will be default and applied to Bills. If you wonder how to split those siameses twins?

The idea is simple, make your own duplicate of class APRegisterAlias, and via CacheAttached add it to needed screen. Below goes code, that I've used for this purpose:

public class APPaymentEntryExt : PXGraphExtension<APPaymentEntry>
    {
        [PXDBString(15, InputMask = ">CCCCCCCCCCCCCCC", IsKey = true, IsUnicode = true)]
        [PXDefault]
        [PXUIField(DisplayName = "Reference Nbr.1", TabOrder = 1, Visibility = PXUIVisibility.SelectorVisible)]
        [APInvoiceType.RefNbr(typeof(Search2<APRegisterAliasSep.refNbrInnerJoinSingleTable<APInvoiceOn<APInvoice.docType, 
            Equal<APRegisterAliasSep.docType>, And<APInvoice.refNbrEqual<APRegisterAliasSep.refNbr>>>, 
            InnerJoinSingleTable<VendorOn<APRegisterAliasSep.vendorIDEqual<Vendor.bAccountID>>>>, 
            Where<APRegisterAliasSep.docTypeEqual<Optional<APInvoice.docType>>, 
                And2<Where<APRegisterAliasSep.origModuleNotEqual<BatchModule.moduleTX>, Or<APRegisterAliasSep.released, 
                    Equal<True>>>, And<Match<VendorCurrent<AccessInfo.userName>>>>>, OrderBy<Desc<APRegisterAliasSep.refNbr>>>), 
            Filterable = true, IsPrimaryViewCompatible = true)]
        [APPaymentType.Numbering]
        [PXFieldDescription]
        protected void APPayment_RefNbr_CacheAttached(PXCache sender)
        {
        }
    }
 
    [PXHidden]
    [PXPrimaryGraph(new[] { typeof(APQuickCheckEntry), typeof(TXInvoiceEntry), typeof(APInvoiceEntry), typeof(APPaymentEntry) }, 
        new[] { typeof(Select<APQuickCheckWhere<APQuickCheck.docTypeEqual<Current<APQuickCheck.docType>>, 
            And<APQuickCheck.refNbrEqual<Current<APQuickCheck.refNbr>>>>>), typeof(Select<APInvoice, 
                Where<APInvoice.docTypeEqual<Current<APInvoice.docType>>, 
                    And<APInvoice.refNbrEqual<Current<APInvoice.refNbr>>, 
                        And<Where<APInvoice.releasedEqual<False>, And<APRegister.origModule, 
                            Equal<BatchModule.moduleTX>>>>>>>), typeof(Select<APInvoiceWhere<APInvoice.docType, 
                                Equal<Current<APInvoice.docType>>, And<APInvoice.refNbr, 
                                    Equal<Current<APInvoice.refNbr>>>>>),
            typeof(Select<PX.Objects.AP.APPaymentWhere<PX.Objects.AP.APPayment.docType, 
                Equal<Current<PX.Objects.AP.APPayment.docType>>, And<PX.Objects.AP.APPayment.refNbr, 
                    Equal<Current<PX.Objects.AP.APPayment.refNbr>>>>>) })]
    [Serializable]
    public class APRegisterAliasSep : APRegister
    {
        public abstract class selected : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class hidden : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class branchID : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class docType : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class printDocType : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class refNbr : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class origModule : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class docDate : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class origDocDate : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class tranPeriodID : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class finPeriodID : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class vendorID : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class vendorID_Vendor_acctName : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class vendorLocationID : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class curyID : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class aPAccountID : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class aPSubID : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class lineCntr : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class curyInfoID : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class curyOrigDocAmt : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class origDocAmt : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class curyDocBal : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class docBal : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class discTot : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class curyDiscTot : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class docDisc : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class curyDocDisc : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class curyOrigDiscAmt : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class origDiscAmt : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class curyDiscTaken : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class discTaken : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class curyDiscBal : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class discBal : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class curyOrigWhTaxAmt : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class origWhTaxAmt : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class curyWhTaxBal : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class whTaxBal : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class curyTaxWheld : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class taxWheld : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class curyChargeAmt : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class chargeAmt : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class docDesc : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class createdByID : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class createdByScreenID : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class createdDateTime : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class lastModifiedByID : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class lastModifiedByScreenID : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class lastModifiedDateTime : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class Tstamp : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class docClass : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class batchNbr : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class prebookBatchNbr : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class voidBatchNbr : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class released : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class openDoc : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class hold : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class scheduled : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class voided : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class printed : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class prebooked : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class noteID : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class refNoteID : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class closedFinPeriodID : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class closedTranPeriodID : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class rGOLAmt : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class curyRoundDiff : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class roundDiff : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class curyTaxRoundDiff : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class taxRoundDiff : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class status : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class scheduleID : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class impRefNbr : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class isTaxValid : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class isTaxPosted : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class isTaxSaved : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class origDocType : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class origRefNbr : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class releasedOrPrebooked : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class taxCalcMode : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class approved : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class rejected : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class dontApprove : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class employeeWorkgroupID : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class employeeID : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class workgroupID : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class ownerID : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class curyInitDocBal : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class initDocBal : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class displayCuryInitDocBal : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class isMigratedRecord : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class curyDiscountedDocTotal : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class discountedDocTotal : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class curyDiscountedTaxableTotal : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class discountedTaxableTotal : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class curyDiscountedPrice : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class discountedPrice : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class hasPPDTaxes : IBqlFieldIBqlOperand
        {
        }
 
        public abstract class pendingPPD : IBqlFieldIBqlOperand
        {
        }
    }

and as result, I've got two filters splitted. 

No Comments

Add a Comment

Acumatica Test Framework In Acumatica Part 2 Practice

Acumatica Test Framework in Acumatica. Part 2 (Practice)

In this part we implement real automation test for creating POOrder - screen PO301000;

Before to start, please read first part of rhis post. 

The main steps:

  1. Create solution and add nedeed components from SDK folder
  2. Add new project (Net library) to solution 
  3. Configure generator and run it for create .cs class with all components on screen PO301000
  4. Add POOrder.cs and describe some methods for test
  5. Add the main class (for example F100_Part1_Lesson1.cs) where we describe Execute() method
  6. Run solution, view result of test

       Create solution and add nedeed components from SDK folder

CCreate a test solution by using Visual Studio 2017 as follows:

  1. In Visual Studio 2017, create a new project TestSDKProject with the following parameters:
  2. Project template: Visual C#
  3. Framework version: .NET Framework 4.7
  4. Project type: Console Application
  5. Project name: TestSDKProject
  6. Solution name: TestSDKProject

 Include references to the following binary libraries (located in the TestProject subfolder of your Test SDK package) in the TestProject project: Execution.dll

At this point your solution should look like shown on the screenshot below:

 As you can see, I also add TestAcumatica.dll.

So create this project.

Add new project (Net library) to solution 

Add a new project TestAcumatica to the created solution with the following parameters:

Project template: Visual C#

Framework version: .NET Framework 4.7

Project type: Class Library

Project name: TestAcumatica

 Project name must start from the word Test

Include references to the following binary libraries (located in the TestProject subfolder of your Test SDK package) in the TestAcumatica project:

PX.QA.Tools.dll

Core.dll

WebDriver.dll

WebDriver.Support.dll

As you can see WebDriver.dll and WebDriver.Support.dll it is selenium web driver ( and you not need to dowload slelenium)

At this point your solution should look like shown on the screenshot below:

Do not forget include reference to the TestAcumatica project in the TestSDKProject project.

Open Program.cs file of the TestProject project (or create a new one if it was not created automatically on step 1) and modify it as follows:

public class Program
    {
        static int Main(string[] args)
        {
            return Execution.Launcher.Main(args);
        }
    }

Configure generator and run it for create .cs class with all components on screen PO301000

Open the ClassGenerator.exe.config file, which is located in the ClassGenerator folder of your Test SDK package, and modify it as follows:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
 <appSettings>
 <!--Local path to the Acumatica ERP instance installation directory-->
 <add key="SitePhysicalPath" value="C:\Program Files (x86)\Acumatica ERP\AcumaticaDBTest"/>
 <!--Output directory to store the generated page wrappers-->
 <add key="GenResultPath" value="C:\share\output"/>
 <!--User to be used for page wrapper generation-->
 <add key="UserName" value="admin@Demo"/>
 <!--IDs of the pages you want to run wrapper generation for; the wildcard * is supported-->
 <add key="FileNameFilter" value="PO301000"/>
 <!--Deletes all files in output directory before running the page wrapper generation process-->
 <add key="ClearOutput" value="true"/>
 <!--Namespace where wrapper classes will be defined. Use the template "GeneratedWrappers.<PartnerName>".-->
 <add key="Namespace" value="GeneratedWrappers.Acumatica"/>
 </appSettings>
</configuration>

If you have more than 1 visible companies in the application instance always specify the company name, e.g.: ; Company name is not required if you have only 1 visible company in the application instance, e.g.

In my situation I have created company - Demo;

After go to your download SDK project - ClassGenerator - and run ClassGenerator.exe

If all will be correct you can see input text - Process DONE - PO301000, and in folder you can see:

After it add folder - Wrappers to TestAcummatica project, and add this PO301000_POOrderEntry.cs file;

What is wrappers?

The page wrapper generation tool creates object mapping model for every page developed by Acumatica Framework. You can access any UI element such as Forms, Grids, Toolbars, data-fields available on a page which you use operating as an ordinary user with Acumatica ERP or any othe Acumatica based product. Test SDK uses built-in control wrapper to construct complex objects such as forms, grids and pages. You need to create wrappers for each page that you want to test.

And for this you use - page wrapper generation tool: You use this tool to create wrappers for the pages of your Acumatica-based product. 

    Add POOrder.cs and describe some methods for test

In this step, you will include extension classes for page wrappers generated earlier into project.

To include extension classes for page wrappers generated earlier into project, do the following:

  1. Create an Extensions folder in the TestAcumatica projec.
  2. 2. Include extension class for generated page wrappers: (just add new POOrder.cs)

public class POOrder : PO301000_POOrderEntry
    {
        public POOrder()
        {
            ToolBar.Save.WaitAction = Wait.WaitForPageToLoad;
        }
 
        public c_document_form Summary
        {
            get
            {
                return base.Document_form;
            }
        }
       public c_transactions_grid Details
        {
            get
            {
                return base.Transactions_grid;
            }
        }
    }

So here we describe constructot for load PO301000 page;

Also Summary and Details - what is it?

And this methods described, generated in PO301000_POOrderEntry (c_document_form, c_transaction_grid) and we return this elements;

Add the main class (for example F100_Part1_Lesson1.cs) where we describe Execute() method

So create F100_Part1_Lesso1.cs and add to Testcumatica project:

public class F100_Part1_Lesson1 : Check
   {
 
       public override void BeforeExecute()
       {
           using (TestExecution.CreateTestCaseGroup("Preparation"))
           {
               using (TestExecution.CreateTestStepGroup("Sign in"))
               {
                   Browser.StartingUrl = Config.SITE_DST_URL;
                   PxLogin LoginPage = new PxLogin();
                   LoginPage.Username.Type(Config.SITE_DST_LOGIN);
                   LoginPage.Password.Type(Config.SITE_DST_PASSWORD);
                   LoginPage.CompanyId.SelectValue("Demo");
                   LoginPage.SignIn.Click();
               }
         
               using (TestExecution.CreateTestStepGroup("Testing: Poorders"))
               {
                   POOrder poOrder = new POOrder();
                   poOrder.OpenScreen();
                   poOrder.Insert();
                   poOrder.Summary.OrderType.Select("Normal");
                   poOrder.Summary.VendorID.Type("BETAAIR");
                   poOrder.Summary.VendorRefNbr.Type("TEST Vendor REF");
                   poOrder.Details.ToolBar.New.Click();               
                   poOrder.Details.Row.BranchID.Type("HQ");
                   poOrder.Details.Row.InventoryID.Select("CARRENT");
                   poOrder.Details.Row.OrderQty.Type("100");
                   poOrder.Details.Row.CuryUnitCost.Type("20");
                   poOrder.Details.Row.ProjectID.Type("INTERNAL18");
                   poOrder.Details.Row.TaskID.Type("01OPS");
 
                   poOrder.Save();
               }
 
               
               }
           }
       }
 
       //Lesson 1: Introduction to General Ledger.
       //Add test-specific logic in this method.
       public override void Execute()
       {
           
}        //This method executes after the test.        public override void AfterExecute()        {                   }    }

Ok, in BeforeExecute() I described my test for creating POOrder, you can do it in Execute() method of course, now it is no matter)

And we ready to run this test.

Run solution, view result of test

You can run test in Chrome or Firefox browser,  in this case I use Firefox.

Fot it - Modify RunnerExample.xml located in Test SDK package as follows:

<?xml version="1.0" encoding="utf-8"?>
<config>
 <general>
 <browser>*firefox</browser>
 <browserbin>C:\Users\xarec\Downloads\TestSDK_17_209_0028_21\Firefox\firefox.exe</browserbin>
 <browser_downloads_folder>c:\share\download\</browser_downloads_folder>
 <site_dst>
 <url>http://localhost/AcumaticaDBTest</url>
 <login>admin</login>
 <pswd>Qwerty!123</pswd>
 </site_dst>
 <logging>
 <logStorage type="htmlfile" level="INFO" outputFolder="c:\share\logStorage" screenshotActive="true" />
 </logging>
 </general>
 <testing>
 <Check Name="F100_Part1_Lesson1"/>
 </testing>
</config>

After click rigth button on TestSDKProject, choose properties and go to Debug and past your path to RunnerExample in Command Line Arguments:

/TestSDKProject.exe

/config "C:\Users\xarec\Downloads\TestSDK_17_209_0028_21\RunnerExample.xml"

Build solution and click Start. If all configure ok, you will see that Firefox will open, open Acumatica, sign in, and create a new POOrder:

After you can go to share - logStorage and will see the result:

So here we can see that all ok, if some operation executed ok, so it will be black color and screensot

POOrder is save. But what will be, if we describe not correct data, or something not correct?

In the LOG we can view it, color will be read.

That`s all for first meet with Acumatica Test Framework, thank you for reading:)

8 Comments

  • Diva said

    Hi yuriy I have a question

    I'm try create PO301000_POOrderEntry.cs file but I can't it, I don't know that I did bad :(

    Can you help me?

  • Diva said

    Hi, me again

    well, I try this example but....


    I do not know what happened that my instance it have next error


    Server Error in '/rembolso' Application.
    Access is denied.
    Description: An error occurred while accessing the resources required to serve this request. The server may not be configured for access to the requested URL.

    Error message 401.2.: Unauthorized: Logon failed due to server configuration. Verify that you have permission to view this directory or page based on the credentials you supplied and the authentication methods enabled on the Web server. Contact the Web server's administrator for additional assistance.

    Version Information: Microsoft .NET Framework Version:4.0.30319; ASP.NET Version:4.7.3282.0

    :( Can you help me? :(

  • docotor said

    Do you can open your instance at manually in browser?

  • Diva said



    Hi, I can't

    I not finish my test yet,I think this causes the error.

    Actually but I can see the file PO301000_POOrderEntry.cs, the test finally created it I did not finish processing the program when I wrote you.

    but I was want open my instance in browser and showed me this error...

  • Diva said

    Excuse me but, I have another error. LOL

    I did modify my Runner Example.xml but when I want run my app. It show me this error

    core.exceptions.ExecitionException: The /config parameter is not specified

    en Exection.Laucher.Main(String[] args) en E:\Bld\AC-TESTSDK2018R109-JOB1\test\Selenium\Execution\Launcher.cs linea a 33

    But I don't get it, if Exection is an DLL

  • docotor said

    Sorry, but for me is a bit hard to follow you and what issue you are facing.

  • Diva said

    I'm sorry, I know there are many problems :(

    but my first problem is solved, I mean I cant see PO301000_POOrderEntry.cs file created by ClassGenerator.exe

    So I try the step "Run solution, view result of test" but when modify my RunnerExample.xml file I have this error:

    core.exceptions.ExecitionException: The /config parameter is not specified

    en Exection.Laucher.Main(String[] args) en E:\Bld\AC-TESTSDK2018R109-JOB1\test\Selenium\Execution\Launcher.cs

    Too my instance say Access deneged and I can't run it manually in browser :(

  • docotor said

    Such testings should be done initially in your local instance, and then applied to your instance to which you don't have access.

Add a Comment

Make Printing Of One Table Per Page With Css

Make printing of one table per page with CSS

Hello erybody,

today I want to share one important note, which took significant amount of time from me to finish.

I had page which as html output few tables. For me it was nessesary to make one page per table. In order to achieve it I've found following CSS:

<style>
    table,
    table tr td,
    table tr th {
        page-break-insideavoid;
    }
</style>

That was only one css that worked for me.

No Comments

Add a Comment

How To Add Database Context To Filter In Net Core 2 0

How to add Database context to Filter in .Net Core 2.0

Hello everybody,

today I want to write a short description on how to add database context to IAuthorizationFilter.

Imagine that you have following filter:

public class HasApprovedFilter : AttributeIAuthorizationFilter
{
    private SomeDbContext _context;
    public HasApprovedFilter(SomeDbContext context)
    {
        this._context = context;
    }
 
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var user = context.HttpContext.User;
        if (!user.Identity.IsAuthenticated)
        {
            return;
        }
 
        var userAdd = _context.;
 
        //var um = context.HttpContext.RequestServices.GetService();
    }
}

As you can see, that filter requires in constructor SomeDbContext. How to add pass it to this filter?

Basically you'll need two steps:

1. At Startup class, in method ConfigureServices add following line:

services.AddScoped<HasApprovedFilter>();

right before line services.AddMvc();

2. At needed controller or Action of controller you can make like this:

[ServiceFilter(typeof(HasApprovedFilter))]
public class HistoryController : Controller

such approach will allow you to have access to database at Filter level. 

Summary

ServiceFilter is entity which takes care about connecting your filter to dependency injection container. You can use it in order to pass not only DataBaseContext, but some other staff.

No Comments

Add a Comment

Types Of Fliters In Net Core

Types of fliters in .Net Core

Hello everybody,

today I want to make a short document about filters in .Net Core.

There are by default four types of filters:

  1. Filters that implement IAuthorizationFilter which allow us to implement OnAuthorization method which allow us to add custom security logic.
  2. Filters that implement IActionFilter has two methods: OnActionExecuting, OnActionExecuted. Those two methods executed before and after action executing.
  3. Filters that implement IExceptionFilter has method OnException, which allows to handle exceptions.
  4. Filters that implement IResultFilter has two methods: OnResultExecuting, OnResultExecuted.

Filters can be applied at controller or at action level. That gives following distinction in their lifetime, if some filter is applied at Controller level, then it will be executed at each action of a controller, which means some filter can make additional workload on your code.

Below goes example of creating filter, which by default generates exception:

public class HasApprovedFilter : AttributeIAuthorizationFilter
{
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        throw new NotImplementedException();
    }
}

That filter is useless, but take note of class Attribute. 

General convention of IAuthorizationFilter is like this: in case if Authorization is correct, then method returns void, if not, we need to set result value on the context object.

No Comments

Add a Comment

How To Override Part Of C Code In Automation Steps In Acumatica

How to override part of C# code in Automation Steps in Acumatica

Hello everybody,

today I want to share one interesting gotcha which took plenty of efforts from mine side in order to understand it.

Recently I got an assignment to modify behaviour of Create Prepayment action at purchase orders. 

If to look at CreatePreapyment implementation, it has following part of code:


if (this.Document.Current == null)
  return;
this.Save.Press();
APInvoiceEntry instance = PXGraph.CreateInstance<APInvoiceEntry>();
if (this.Document.Current.PrepaymentRefNbr == null)
{

if to put simply it checks if current purchase order already has a prepayment, and in case if it has, then give to a user error message. 

Imagine, that you need to modify that behaviour for some reason. How can you achieve it? One of the ways is to modify C# code, which is pretty good way, and workable. 

But let's try another way, automation steps.

Before I'll continue I want to say very important think: Automation Steps has priority over anything. It means that in case of conflict, Automation steps instruction will have priority over anything.

If your code says to Acumatica to do A, but automation step says to do B, Acumatica will do B. If your security settings says to do A, but Automation step says to do B, Acumatica will do B, and not A.

I can continue that list on and on but remember, Automation steps has priority over anything. 

For me they become some kind of pain points especially when dealing with Purchase orders. 

Take a look at another screenshot:

basically it says that on Step NL Prepaid action CreatePrepayment will be disabled. 

And whatever you'll do with a code or security, or even js hacking, menu item Create Prepayment will be disabled.

But if you take out checkbox Disabled, then Create Prepayment will become enabled.

Now let's back to the question, how to set value of PrepaymentRefNbr to null with Automation Steps.

You can use following steps for this:

  1. go to Automation steps screen
  2. Choose for Screen ID Purchase Orders
  3. For Step ID type NL Prepaid
  4. Go to Actions tab, and find there Action CreatePrepayment.
  5. Click on Fill with values as shown on the screen:

6. In the pop up window pick PrepaymentRefNbr as shown at screenshot:

7. Click on close.

After all of those manipulations in debugger ( well, not only in debugger ) you'll eyewitness miracle, value of PrepaymentRefNbr will be null independently of created before Prepayments.

also if to look at tab other information PrepaymentRefNbr showed something different:

as you can see from screenshot Prepayment Ref Nbr wasn't empty, in reality it showed as empty.

Conclusion

As usually I don't write conclusions, but here I want to write a few. First of all, if you write some code in Acumatica and it doesn't behave as you'd like, take a look at Automation steps, maybe there is the reason of your problems.

Second, if you want to override some code and don't want to create customization, C# and so on, take a look at Automation steps. With such a trick you can quickly help to your customer and also puzzle him about level of your knowledge of Acumatica.

No Comments

Add a Comment

How To Override Create Prepayment In Acumatica Without Usage Of Custom Delegate

How to override Create Prepayment in Acumatica without usage of custom delegate

Hello everybody,

today I want to share with you on how to override Action CreatePrepayment in Acumatica.

General rule of overriding methods in Acumatica according to T300 manual is like this:

  1. Create delegate.
  2. Add [PXOverride] over your method which is named exactly as base method ( in our case CreatePrepayment )
  3. Add your implementation

For example you can achieve it like this:

//Create your delegate
public delegate void CreatePrepaymentDelegate();
 
[PXOverride]
public void CreatePrepayment(CreatePrepaymentDelegate baseDel)
{
    // your code of overriding
}

Way from manual T300 is perfectly workable, but  I propose you to use feature of .Net which is named Action, which is declared very much exactly as mentioned delegate. 

Then your code for overriding will look pretty much the same, you'll just need use Action and omit delegate declaration:

[PXOverride]
public void CreatePrepayment1(Action baseDel)
{
         // your code of overriding
}

with such simple trick you can save a bit of time on typing.

No Comments

Add a Comment