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

 

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
 

 

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
 

 

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
 

 

Creating Custom Autonumber Autogenerated Id In Acumatica

 

If you want to create custom Attribute for autonumbering field you need:

  1. Create Setup page for configuring autonumbering field or maybe you can use existing
  2. Create Attribute which you'll add to your entity
  3. Add attribute to field that you need increment 

Create Setup page for configuration autonumbering field

Setup.cs like this:

    [System.SerializableAttribute()]
    [PXPrimaryGraph(typeof(CurrencyMaint))]
    [PXCacheName("Your company Preferences")]
    public class Setup : PX.Data.IBqlTable
    {
        #region DocumentRefNbr
        public abstract class documentRefNbr : PX.Data.IBqlField
        {
        }
        protected string _DocumentRefNbr;
        [PXDBString(15, IsUnicode = true)]
        [PXDefault("00010")]
        [PXUIField(DisplayName = "Document Last Ref. Number")]
        public virtual string DocumentLastDocNbr
        {
            get
            {
                return this._DocumentRefNbr;
            }
            set
            {
                this._DocumentRefNbr = value;
            }
        }
        #endregion
        #region ReturnDocRefNbr
        public abstract class returnLastDocNbr : PX.Data.IBqlField
        {
        }
        protected string _ReturnLastDocRefNbr;
        [PXDBString(15, IsUnicode = true)]
        [PXDefault("00010")]
        [PXUIField(DisplayName = "Return Documnet Ref. Number")]
        public virtual string ReturnLastDocNbr
        {
            get
            {
                return this._ReturnLastDocRefNbr;
            }
            set
            {
                this._ReturnLastDocRefNbr = value;
            }
        }
        #endregion
        
        #region AutoNumbering
        public abstract class autoNumbering : PX.Data.IBqlField
        {
        }
        protected bool? _AutoNumbering;
        [PXDBBool()]
        [PXDefault(true, PersistingCheck = PXPersistingCheck.Nothing)]
        [PXUIField(DisplayName = "Auto Numbering")]
        public virtual bool? AutoNumbering
        {
            get
            {
                return this._AutoNumbering;
            }
            set
            {
                this._AutoNumbering = value;
            }
        }
        #endregion
    }

We create 3 fields:

  1. DocumentLastDocNbr - for setting start position to numbering;
  2. ReturnLastDocNbr - for get last number from db
  3. AutoNumbering bool field for setting auto or manual numbering

Next, create graph SetupMaint for Setup page:

public class SetupMaint : PXGraph<SetupMaint>
    {
        public PXSave<Setup> Save;
        public PXCancel<Setup> Cancel;
 
        public PXSelect<Setup> LastNumbers;
    }

Next, create view, page YC101000:

<%@ Page Language="C#" MasterPageFile="~/MasterPages/FormView.master" AutoEventWireup="true" ValidateRequest="false" CodeFile="YC101000.aspx.cs" Inherits="Page_YC101000" Title="Untitled Page" %>
<%@ MasterType VirtualPath="~/MasterPages/FormView.master" %>
 
<asp:Content ID="cont1" ContentPlaceHolderID="phDS" Runat="Server">
	<px:PXDataSource ID="ds" runat="server" Visible="True" Width="100%" TypeName="CurrencyApplication.SetupMaint" PrimaryView="LastNumbers">
	</px:PXDataSource>
</asp:Content>
<asp:Content ID="cont2" ContentPlaceHolderID="phF" Runat="Server">
	<px:PXFormView ID="form" runat="server" DataSourceID="ds" Style="z-index100" Width="100%" DataMember="LastNumbers" TabIndex="800">
		<Template>
			<px:PXLayoutRule runat="server" StartRow="True" ControlSize="SM" LabelsWidth="SM"/>
			<px:PXTextEdit ID="edLastDocNbr" runat="server" DataField="DocumentLastDocNbr">
			</px:PXTextEdit>
			<px:PXTextEdit ID="edReturnLastDocNbr" runat="server" DataField="ReturnLastDocNbr">
			</px:PXTextEdit>
		   <px:PXCheckBox ID="edAutoNumbering" runat="server" AlignLeft="True" DataField="AutoNumbering" Text="Auto Numbering">
			</px:PXCheckBox>
		</Template>
		<AutoSize Container="Window" Enabled="True" MinHeight="200" />
	</px:PXFormView>
</asp:Content>

                     

 

Create Attribute

After it, you can create attribute:

public class AutoNumberAttribute : PXEventSubscriberAttribute,
                                        IPXFieldDefaultingSubscriberIPXFieldVerifyingSubscriber,
                                        IPXRowPersistingSubscriberIPXRowPersistedSubscriber
    {
        public const string NewValue = "";
 
        private bool _AutoNumbering;
        private Type _AutoNumberingField;
        private BqlCommand _LastNumberCommand;
 
     
        public virtual Type LastNumberField { getprivate set; }
        public static void SetLastNumberField<Field>(PXCache sender, object row, Type lastNumberField)
            where Field : IBqlField
        {
            foreach (PXEventSubscriberAttribute attribute in sender.GetAttributes<Field>(row))
            {
                if (attribute is AutoNumberAttribute)
                {
                    AutoNumberAttribute attr = (AutoNumberAttribute)attribute;
                    attr.LastNumberField = lastNumberField;
                    attr.CreateLastNumberCommand();
                }
            }
        }
 
       public AutoNumberAttribute(Type autoNumbering)
        {
            if (autoNumbering != null &&
                (typeof(IBqlSearch).IsAssignableFrom(autoNumbering) ||
                 typeof(IBqlField).IsAssignableFrom(autoNumbering) && autoNumbering.IsNested))
            {
                _AutoNumberingField = autoNumbering;
            }
            else
            {
                throw new PXArgumentException("autoNumbering");
            }
        }
 
        public AutoNumberAttribute(Type autoNumbering, Type lastNumberField)
            : this(autoNumbering)
        {
            LastNumberField = lastNumberField;
            CreateLastNumberCommand();
        }
 
        private void CreateLastNumberCommand()
        {
            _LastNumberCommand = null;
 
            if (LastNumberField != null)
            {
                if (typeof(IBqlSearch).IsAssignableFrom(LastNumberField))
                    _LastNumberCommand = BqlCommand.CreateInstance(LastNumberField);
                else if (typeof(IBqlField).IsAssignableFrom(LastNumberField) && LastNumberField.IsNested)
                    _LastNumberCommand = BqlCommand.CreateInstance(typeof(Search<>), LastNumberField);
            }
 
            if (_LastNumberCommand == nullthrow new PXArgumentException("lastNumberField");
        }
 
        public override void CacheAttached(PXCache sender)
        {
            BqlCommand command = null;
            Type autoNumberingField = null;
            if (typeof(IBqlSearch).IsAssignableFrom(_AutoNumberingField))
            {
                command = BqlCommand.CreateInstance(_AutoNumberingField);
                autoNumberingField = ((IBqlSearch)command).GetField();
            }
            else
            {
                command = BqlCommand.CreateInstance(typeof(Search<>), _AutoNumberingField);
                autoNumberingField = _AutoNumberingField;
            }
            PXView view = new PXView(sender.Graph, true, command);
            object row = view.SelectSingle();
            if (row != null)
            {
                _AutoNumbering = (bool)view.Cache.GetValue(row, autoNumberingField.Name);
            }
        }
 
        public virtual void FieldDefaulting(PXCache sender, PXFieldDefaultingEventArgs e)
        {
            if (_AutoNumbering)
            {
                e.NewValue = NewValue;
            }
        }
 
        public virtual void FieldVerifying(PXCache sender, PXFieldVerifyingEventArgs e)
        {
            if (_AutoNumbering && PXSelectorAttribute.Select(sender, e.Row, _FieldName, e.NewValue) == null)
            {
                e.NewValue = NewValue;
            }
        }
 
        protected virtual string GetNewNumber(PXCache sender, Type setupType)
 
        { 
 
            if (_LastNumberCommand == null)
                CreateLastNumberCommand();
            PXView view = new PXView(sender.Graph, false, _LastNumberCommand);
            object row = view.SelectSingle();
            if (row == nullreturn null;
            long number;
 
            string lastNumber = (string)view.Cache.GetValue(row, LastNumberField.Name);
            number = Int64.Parse(lastNumber);
            number = number + 3;
 
         lastNumber = "0000" + Convert.ToString(number);
 
            view.Cache.SetValue(row, LastNumberField.Name, lastNumber);
            PXCache setupCache = sender.Graph.Caches[setupType];
            setupCache.Update(row);
            setupCache.PersistUpdated(row);
            return lastNumber;
        }
 
        public virtual void RowPersisting(PXCache sender, PXRowPersistingEventArgs e)
        {
            if ((e.Operation & PXDBOperation.Command) == PXDBOperation.Insert)
            {
                Type setupType = BqlCommand.GetItemType(_AutoNumberingField);
                string lastNumber = GetNewNumber(sender, setupType);
                if (lastNumber != null)
                {
                    sender.SetValue(e.Row, _FieldOrdinal, lastNumber);
                }
            }
        }
 
        public virtual void RowPersisted(PXCache sender, PXRowPersistedEventArgs e)
        {
            if ((e.Operation & PXDBOperation.Command) == PXDBOperation.Insert &&
                e.TranStatus == PXTranStatus.Aborted)
            {
                sender.SetValue(e.Row, _FieldOrdinal, NewValue);
                Type setupType = BqlCommand.GetItemType(_AutoNumberingField);
                sender.Graph.Caches[setupType].Clear();
            }
        }
    }

Pay attention that for correct work you must implement interfaces, and ovveride standart methods(RowPersisting.... etc, all that need)

Main function for generating new walue is GetNewNumber; New walue will be bigger on 3;

Ok, here we described method - 

SetLastNumberField()

This method we call from graph before when we click save data to database in event RowPersisting:

Add attribute to field that you need increment;

public class CurrencyMaint : PXGraph<CurrencyMaintUsrCurrency>
    {
 
        public PXFilter<ExchangeDateFilter> Filter;
        public PXSelect<UsrCurrencyWhere<UsrCurrency.currencyExchangeDateEqual<Current<ExchangeDateFilter.exchangeDate>>>> Currencies;
 
        public PXSetup<Setup> AutoNumSetup;
 
 
        public CurrencyMaint()
        {
            Setup setup = AutoNumSetup.Current;
        }
 
        protected virtual void UsrCurrency_RowPersisting(PXCache sender, PXRowPersistingEventArgs e)
        {
            UsrCurrency doc = (UsrCurrency)e.Row;
            if (sender.GetStatus(doc) == PXEntryStatus.Inserted)
            {
                AutoNumberAttribute.SetLastNumberField<UsrCurrency.extRefNbr>(
                    sender, doc,
                    typeof(Setup.returnLastDocNbr));
            }
        }
    }

So for example I have DAC class and field ExtRefNbr I want to set autonumber attrbute, to do this try: [AutoNumber(typeof(Setup.autoNumbering))]

Of course don`t forget to create aspx page :)

[System.SerializableAttribute()]
    public class UsrCurrency : PX.Data.IBqlTable
    {
        
        public abstract class extRefNbr : PX.Data.IBqlField
        {
 
        }
        protected string _ExtRefNbr;
        [PXDBString(40, IsUnicode = true, InputMask = ">CCCCCCCCCCCCCCC")]
        [PXDefault("00000000")]
        [PXUIField(DisplayName = "Document Ref.")]
         [AutoNumber(typeof(Setup.autoNumbering))]
        public virtual string ExtRefNbr
        {
            get
            {
                return this._ExtRefNbr;
            }
            set
            {
                this._ExtRefNbr = value;
 
            }
        }
 
        #region CurrencyID
        #endregion         #region CurrencyName         #endregion         #region CurrencyRate             #endregion         #region CurrencyCC                 #endregion     }

And after configure Setup page:

And try to add new rows, and test autonumber:

Enjoy the result )

2 Comments

  • Angie said

    Hi Yuriy,

    Excuse me, I have a question...

    What is ExchangeDateFilter?.

    But I don't know how implement these filters

    I hope your answer. Thank very much :)

  • docotor said

    Update:

    below goes class ExchangeDateFilter:

    [Serializable]
    public class ExchangeDateFilter : IBqlTable
    {
    #region ExchangeDate
    public abstract class exchangeDate : PX.Data.IBqlField
    {
    }
    [PXDate()]
    [PXDefault( typeof(AccessInfo.businessDate))]
    [PXUIField(DisplayName = "Currency ExchangenDate")]
    [PXSelector(
    typeof(Search<UsrCurrency.currencyExchangeDate>))]

    public virtual DateTime? ExchangeDate { get; set; }
    #endregion
    }

    This filter not need for autonumbering, it is for select all from UsrCurrency table where date equal current date.

Add a Comment
 

 

New Class In Acumatica Pximpersonationcontext

 

Hello everybody,

here I want to document new scope in Acumatica: PXImpersonationContext. 

As often is the case try to look at presented code:

var thr = new Thread(
                                   () =>
                                   {
                                       try
                                       {
                                           using (new PXImpersonationContext(PX.Data.Update.PXInstanceHelper.ScopeUser))
.

.

.

}
                            foreach (var thread in threads)
                            {
                                lock (thisLock)
                                {
                                    thread.Start();
                                }
                            }
 
                            foreach (var thread in threads)
                            {
                                thread.Join();
                            }

It does the following: creates threads, inside of threads users some logic for persistance to database through graphs and then waits for joining threads.

Recently I've had following problem. I've created processing screen that worked great in manual mode, but absoultely refused to work in Automation schedule mode. Refused means thread was crashed without any exception message.

Adding PXImpersonationContext solved the issue for me.

Now you may wonder, how PXImpersonationContext achieves it?

Let's consult with JetBrains decompiler:

public class PXImpersonationContext : IDisposable
{
  private IPrincipal a;
  private readonly WindowsImpersonationContext b;
 
  public PXImpersonationContext()
    : this(PXDatabase.Companies.Length != 0 ? "sys@" + PXDatabase.Companies[0] : "sys")
  {
  }
 
  public PXImpersonationContext(string userName)
    : this(userName, new string[0])
  {
  }
 
  public PXImpersonationContext(string userName, string[] roles)
  {
    WindowsIdentity windowsIdentity = (WindowsIdentitynull;
    IPrincipal principal = (IPrincipalnew GenericPrincipal((IIdentitynew GenericIdentity(userName), roles);
    IdentitySection section = (IdentitySection) WebConfigurationManager.GetSection("system.web/identity");
    if (section != null && section.Impersonate && HttpContext.Current != null)
    {
      HttpWorkerRequest service = (HttpWorkerRequest) ((IServiceProvider) HttpContext.Current).GetService(typeof (HttpWorkerRequest));
      if (service != null)
      {
        IntPtr userToken = service.GetUserToken();
        if (userToken != IntPtr.Zero)
          windowsIdentity = new WindowsIdentity(userToken);
      }
    }
    this.a = PXContext.PXIdentity.User;
    PXContext.PXIdentity.User = principal;
as you can see from decompiled code sample, PXImpersonationContext uses user name "sys@currentlyloggedcompany" then it tries to create WindowsIdentity user and then uses that user for processing your staff.
Pretty cool idea, huh?
One more addition.
PX.Data.Update.PXInstanceHelper.ScopeUser also will give you "sys@currentlyloggedcompany" which leads me to to conclusion that "sys@currentlyloggedcompany" is most cool user in Acumatica system.
Also it is not presented in table users which means that such a user is some kind of virtual user.

If you become puzzled from this long technical description, then go straight to the summary:
Summary

If your processing screen crashes with no reason in automation schedule mode, add to your code 

using (new PXImpersonationContext(PX.Data.Update.PXInstanceHelper.ScopeUser))

{

    // here shold be your processing logic

}

and try again. In mine case such addition "resurected" mine automation schedules. Hopefully yours will also come back to life.

No Comments

 

Add a Comment
 

 

Required Instead Of Current In Acumatica

 

Hello everybody,

today I want to fulfill my promise that I gave to one of my readers. 

He left at mine blog following question:

How do we use required<> in place of current<> and pass some string constant for selector? That question was asked in context of this article. 

That is good question and I also want to add that answer on it will be at least to some degree disappointing. First of all, if you work with selectors, you can't use Required. Required is intended for use in Graphs.

But if you want to use some constant filtering conditions, you don't need Required attribute at all. You can use mechanism of Constants, which works perfectly without Required. 

Take a look at the following declaration:

public static class OrderTypes
{
    public const string N = "N";
    public const string C = "C";
 
    public class closed : Constant<String>
    {
        public closed()
            : base(C)
        {
        }
    }
 
    public class open : Constant<String>
    {
        public open() : base(N)
        {
        }
    }
}

As you can see, we declared two cosntants for order types: N and C.

Below goes code that shows how can you create selector that will filter Orders by OrderTypes with usage of those constants:

class Test : PXCacheExtension<SOOrder>
{
    #region UsrPreviousTermsId
 
    public abstract class usrPreviousTermsId : IBqlField
    {
    }
 
    [PXSelector(typeof(Search<SOOrder.orderNbrWhere<SOOrder.statusEqual<OrderTypes.closed>>>))]
    [PXUIField(DisplayName = "Linked sales order")]
    [PXDBString(10, IsUnicode = true)]
    public string UsrLinkedSalesOrder { getset; }
 
    #endregion
}

Such code will give you selector that give you a list of orders with status closed. Also this sample of code shows that for selector you don't need Required in order to filter by some constant value.

 

 

1 Comment

Add a Comment
 

 

How To Check Automation Step Settings In Acumatica

 

Hello everybody,

today I want to describe some tricky feature of Automation steps. 

Quite often in Acumatica I face interesting challenge. I've added some code in Row_Selected, open some screen, and to mine disappointment I find that screen totally ignores mine code. What can stand behind such weird behaviour?

One of the explanations can lie behind automation steps. If to put simply, automation steps is a feature of Acumatica, that allows to program it without any usage of C# code. All that is needed from you as Acumatica user is just add via GUI designer staff on the form and then via automation steps configure it's behaviour. 

So, let's consider some details, that can help you to understand how automation steps work better. 

First step, that I propose you to do, is add following line to your web.config in appSettings section:

  <appSettings>
    <clear />
    <add key="AutomationDebug" value="true" />

with such a setting you'll see hint from Acumatica on each screen:

In top left corner you'll be able to see, which Automation step you need to edit.

Then you can go to screen Automation steps, and search for step IN Completed. For example you can do it like this:

After that you'll see screen with three tabs:

First tab tells you under which conditions two other tabs: Actions and fields will work. Let's dig deeper in this particular case. 

Current tab says that when you open invoice ( Behavior Equals to invoice ) and status of that Invoice is completed, let's switch to tab Actions:

following Actions should be Active:

Report, Copy Order, VlidateAddresses, Email Sales Order/Quote.

Also it says that Action Create Shipment, Open Order, Cancel Order will be inactive. 

Also it gives you idea, that in field Menu Text you can change Text of Actions to something different. For example instead of Print Sales Order/Quote you can type Print Document. Also you can assign some icon to that menu item.

Before those steps Menu Item in Report looks like this:

After you'll try those steps, you'll see the following:

If you have question, why this menu item wasn't renamed and just new menu item appeared, stay assured that I also have. Maybe somebody from Acumatica can give good answer as a comment. For now I also puzzled why. If you'll discover why in the future, please find the time to comment on this blog.

One final point of Automation steps is tab Fields. Take a look at particular Automation step In Completed.:

As you can see, and probably guess, during "In Completed" automation step almost everything is in state disabled. 

No Comments

Add a Comment
 

 

Pxprpojection Data Projection What Is It

 

Hello everybody.

Imagine that you need to have implementation of several tables with possibility to update them all. 

Also you want to implement by the Acumatica side rather than by the database.

  • You know that Joined tables in Acumatica are read-only and you cannot update fields there
  • Can we create/use SQL-like views in Acumatica?
  • How can I join grouped (statistical) view to the DAC?

So, yes we can do it. For this purpose Acumatica gives you PXprojection attribute.

PXProjection attribute  binds the DAC to an arbitrary data set. The attribute thus defines a named view, but is implemented by the server side rather than by the database.

Lets start)

Steps:

  • Think whats tables you need to implement and whats of fields from this table;
  • Create new DAC that will represent view. This DAC may have less columns than you have in DACs you will select later. So just define fields you need. In the end it will help a bit with select performance;
  • Create new graph;
  • Create page - view of implementatoin;

For examle, I want to see implementatioin of 2 tables(CRactivity and BAccount) and fields like baccountID, baccountName from BAccount table and contactID, subject from CRactivity table.

In my solution I create new DAC class and define PXProjectionAttribute on this DAC.

 

[Serializable]
    [PXProjection(typeof(
        Select2<BAccount,
            InnerJoin<CRActivity,
                On<CRActivity.bAccountIDEqual<BAccount.bAccountID>>>>),
       Persistent = true
        )]
    public partial class PXprojectionJoinDacClass : IBqlTable
    {
        public abstract class baccountID : PX.Data.IBqlField
        {
        }
        // The field mapped to the BAccount field (through setting of BqlField)
        [PXDBInt(IsKey = true, BqlField = typeof(BAccount.bAccountID))]
        [PXExtraKey]
        [PXUIField(DisplayName = "BAccount ID")]
        public virtual int? BAccountID { getset; }
 
        public abstract class contactID : PX.Data.IBqlField
        {
        }
        // The field mapped to the CRactivity field
        // (through setting of BqlField)
        [PXDBInt(IsKey = true, BqlField = typeof(CRActivity.contactID))]
        [PXUIField(DisplayName = "Contact ID")]
        public virtual int? ContactID { getset; }
 
        public abstract class subject : PX.Data.IBqlField
        {
        }
        // The field mapped to the CRactivity field
        // (through setting of BqlField)
        [PXDBString(IsKey = false, BqlField = typeof(CRActivity.subject))]
        [PXUIField(DisplayName = "Subject")]
        public virtual string Subject { getset; }
 
        public abstract class acctName : PX.Data.IBqlField
        {
        }
        // The field mapped to the Baccount field
        // (through setting of BqlField)
        [PXDBString(IsKey = false, BqlField = typeof(BAccount.acctName))]
        [PXUIField(DisplayName = "Account Name")]
        public virtual string AcctName { getset; }
 
    }

As you see I use Select2 attribute for select from 2 tables and use BQL command. In my way it is InnerJoin.

For PXProjection constructor you should provide BQL command that will define what tables you want to select. You may use all possible commands of BQL (Join, Where, GroupBy, OrderBy).

Also I define "Persistent = true" for update both tables, without it my implementatiioin will be only for read. 

Go next and create new graph.

 

public class ProjectionMaint : PXGraph<ProjectionMaintPXprojectionJoinDacClass>
   {
       public PXSelect<PXprojectionJoinDacClass> Projections;
   }

Easy, we only use PXSelect from PXprojectionJoinDacClass.

Go next and create new page.

 

<%@ Page Language="C#" MasterPageFile="~/MasterPages/FormDetail.master" 
AutoEventWireup="true" ValidateRequest="false"  CodeFile="PR101000.aspx.cs" Inherits="Page_PR101000" Title="Untitled Page" %> <%@ MasterType VirtualPath="~/MasterPages/FormDetail.master" %>   <asp:Content ID="Content1" ContentPlaceHolderID="phDS" runat="Server">     <px:PXDataSource ID="ds" runat="server" Visible="True" SuspendUnloading="False"                       TypeName="ClassLibrary1.ProjectionMaint" PrimaryView="Projections">     </px:PXDataSource> </asp:Content> <asp:Content ID="cont2" ContentPlaceHolderID="phG" runat="Server">     <px:PXGrid ID="grid" runat="server" Height="400px" Width="100%" Style="z-index100"                AllowPaging="True" AllowSearch="True" AdjustPageSize="Auto"
 DataSourceID="ds" SkinID="Primary"                 TabIndex="800" TemporaryFilterCaption="Filter Applied">         <Levels>             <px:PXGridLevel DataKeyNames="BAccountID" DataMember="Projections">                 <Columns>                     <px:PXGridColumn DataField="BAccountID" Width="90px">                         <ValueItems MultiSelect="False">                         </ValueItems>                     </px:PXGridColumn>                     <px:PXGridColumn DataField="ContactID" Width="200px">                         <ValueItems MultiSelect="False">                         </ValueItems>                     </px:PXGridColumn>                     <px:PXGridColumn DataField="Subject" Width="200px">                         <ValueItems MultiSelect="False">                         </ValueItems>                     </px:PXGridColumn>                     <px:PXGridColumn DataField="AcctName" Width="200px">                         <ValueItems MultiSelect="False">                         </ValueItems>                     </px:PXGridColumn>                 </Columns>             </px:PXGridLevel>         </Levels>         <AutoSize Container="Window" Enabled="True" MinHeight="200" />     </px:PXGrid> </asp:Content>

So here we use only PXGrid where we define fields.

After add this page to site map and Go to page PR101000 to test.

All work fine. Make sure that fields updateble in both tables.

Also do not forget that you can use 2 and more tables in one implementation. 

Thank you for reading. Have you question? Please leave comments here.

No Comments

Add a Comment
 

 

How To Separate Automation Schedules In Acumatica

 

Hello everybody,

take a loot at the following picture:

Let's say that you would like to have two Acumatica instances connected to the same database. Is it possible? Definetely yes, just with pointing both of them to the same connection string and you'll get some kind of scalability. 

But imagine that your Acumatica has execution of some automation schedules. How to make sure, that only one of them will be executor of Automation schedules, not both of them?

Very simple. Just add this key to web.config of Acumatica which should not be Automation schedules executor:

 <add key="DisableScheduleProcessor" value="True"/>

Default value of DisableScheduleProcessor key is false, so you need to say which Acumatca instance shouldn't think about execution schedules. With this simple trick you can have two or even more instances of Acumatica and regulate which of those instances will execute schedules

No Comments

Add a Comment