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
 

 

How To Modify Stock Item Screen In202500 In Acumatica

 

Hello everybody,

today I want to describe how to extend Stock Item screen IN202500 in Acumatica. Imagine that you need to add to tab General settings two selectors. Suppose that you need to have two selectors:

as you can see following need to be achieved:

  1. To tab General Settings it is needed to add selectors: "Clase articulo web" and "Subclase articulo web".
  2. In case if selector "Clase articulo web" changes, then "Subclase articulo web" should show some other values.

The first step should be start Acumatica developer project as described here.

For cases if we have dependency of one selector from another it is possible to program in two ways:

  1. Custom selector for dependent code.
  2. Describe dependency in DAC class or DAC class extension.

Option number 1 or custome selectors were already described at mine blog here

Let's take a look at second scenario. Before we continue let's create two tables: UsrArticul and UsrSubArticul. In order to make life simple, you can use SQL below in order to follow me:

SET ANSI_NULLS ON
 
 
SET QUOTED_IDENTIFIER ON
 
 
CREATE TABLE [dbo].[UsrArticul](
	[CompanyID] [INT] NOT NULL,
	[ArticulID] [INT] IDENTITY(1,1) NOT NULL,
	[ArticulCD] [NVARCHAR](50) NULL,
	[ArticulName] [NVARCHAR](50) NULL,
 CONSTRAINT [PK_Articul] PRIMARY KEY CLUSTERED 
(
	[CompanyID] ASC,
	[ArticulID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) 
) 
 
SET ANSI_NULLS ON
GO
 
SET QUOTED_IDENTIFIER ON
GO
 
CREATE TABLE [dbo].[UsrSubArticuls](
	[CompanyID] [int] NOT NULL,
	[ArticulID] [int] NOT NULL,
	[SubArticulID] [int] IDENTITY(1,1) NOT NULL,
	[SubArticulCD] [nvarchar](50) NULL,
	[SubArticulName] [nvarchar](50) NULL,
 CONSTRAINT [PK_UsrSubArticuls] PRIMARY KEY CLUSTERED 
(
	[CompanyID] ASC,
	[ArticulID] ASC,
	[SubArticulID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
) 

I always use prefix Usr in Acumatica in order to notify updater of Acumatica that during upgrade of version those two tables shouldn't be deleted. 

Next let's insert some demo data:

INSERT INTO [dbo].[UsrArticul] ([CompanyID] ,[ArticulCD] ,[ArticulName]) VALUES (2, 'ART1', 'Articul 1' )
INSERT INTO [dbo].[UsrArticul] ([CompanyID],[ArticulCD],[ArticulName]) VALUES (2, 'ART2', 'Articul 2' )
INSERT INTO [dbo].[UsrArticul]([CompanyID],[ArticulCD],[ArticulName]) VALUES (2, 'ART3', 'Articul 3' )
INSERT INTO [dbo].[UsrSubArticuls] ([CompanyID] ,[ArticulID] ,[SubArticulCD] ,[SubArticulName]) VALUES (,,'SUB1A' ,'SUB 1 A')
INSERT INTO [dbo].[UsrSubArticuls] ([CompanyID] ,[ArticulID] ,[SubArticulCD] ,[SubArticulName]) VALUES (,1, 'SUB1b' ,'SUB 1 b')
INSERT INTO [dbo].[UsrSubArticuls] ([CompanyID] ,[ArticulID] ,[SubArticulCD] ,[SubArticulName]) VALUES (,1, 'SUB1c' ,'SUB 1 c')
INSERT INTO [dbo].[UsrSubArticuls] ([CompanyID] ,[ArticulID] ,[SubArticulCD] ,[SubArticulName]) VALUES (,1,  'SUB1d','SUB 1 d')
INSERT INTO [dbo].[UsrSubArticuls] ([CompanyID] ,[ArticulID] ,[SubArticulCD] ,[SubArticulName]) VALUES (2, 2,'SUB2A','SUB 2 A')
INSERT INTO [dbo].[UsrSubArticuls] ([CompanyID] ,[ArticulID] ,[SubArticulCD] ,[SubArticulName]) VALUES (2, 2, 'SUB2b' ,'SUB 2 b')
INSERT INTO [dbo].[UsrSubArticuls] ([CompanyID] ,[ArticulID] ,[SubArticulCD] ,[SubArticulName]) VALUES (2, 2, 'SUB2c' ,'SUB 2 c')
INSERT INTO [dbo].[UsrSubArticuls] ([CompanyID] ,[ArticulID] ,[SubArticulCD] ,[SubArticulName]) VALUES (2, 2, 'SUB2d' ,'SUB 2 d')
INSERT INTO [dbo].[UsrSubArticuls] ([CompanyID] ,[ArticulID] ,[SubArticulCD] ,[SubArticulName]) VALUES (2, 3, 'SUB3A', 'SUB 3 A')
INSERT INTO [dbo].[UsrSubArticuls] ([CompanyID] ,[ArticulID] ,[SubArticulCD] ,[SubArticulName]) VALUES (2, 3, 'SUB3b', 'SUB 3 b')
INSERT INTO [dbo].[UsrSubArticuls] ([CompanyID] ,[ArticulID] ,[SubArticulCD] ,[SubArticulName]) VALUES (2, 3, 'SUB3c', 'SUB 3 c')
INSERT INTO [dbo].[UsrSubArticuls] ([CompanyID] ,[ArticulID] ,[SubArticulCD] ,[SubArticulName]) VALUES (2, 3,'SUB3d', 'SUB 3d')

We have three root articuls and each of those root articuls has 4 child sub articuls. And task is the following, if User select Articul 1 at top selector, then bottom selector should show SUB 1 A, SUB 1 b, Sub 1 c and Sub 1 d. 

In order to have access to those two controls on page IN202500 we need:

  1. Create DAC classes for tables UsrArticule and UsrSubArticuls
  2. Exted DAC class InventoryItem with declaration of two fields as selectors
  3. Add two selectors on the page

In order to create two DAC classes, you can use either standard Acumatica DAC class generator, or download mine utility which does the same. If you decide to use utility then also keep in mind that you need delete from generated code following fields: CreatedByID, CreatedByScreenID, CreatedDatetime, LastModifiedByID, LastModifiedByScreenID, LastModifiedDateTime, Tstamp. I skipped those fields that Acumatica uses in order to make sample easier to understand. But in real life projects I definetely recommend to have those fields especially if you have multiuser environment where few users can modify the same entity. In that case those service fields is a must.

Take a look at two DAC classes about UsrArticul and UsrSubArticuls:

[Serializable]
    public class UsrArticul : IBqlTable 
    {
        #region ArticulID
        public abstract class articulID : IBqlField
        {
        }
        [PXDBInt(IsKey = true)]
        [PXUIField(DisplayName = "Articul ID", Visibility = PXUIVisibility.Visible, Visible = false, Enabled = false)]
        public virtual int? ArticulID { getset; }
 
        #endregion
 
        #region route
        public abstract class articulCD : IBqlField
        {
        }
        [PXDBString(50)]
        [PXUIField(DisplayName = "Articul CD", Visibility = PXUIVisibility.Visible)]
        public virtual string ArticulCD { getset; }
        #endregion
 
        #region route
        public abstract class articulName : IBqlField
        {
        }
        [PXDBString(50)]
        [PXUIField(DisplayName = "Articul Name", Visibility = PXUIVisibility.Visible)]
        public virtual string ArticulName { getset; }
        #endregion
       
    }

and another class:
    [Serializable]
    public class UsrSubArticuls : IBqlTable 
    {
        #region ArticulID
        public abstract class articulID : IBqlField
        {
        }
 
        [PXDBInt()]
        [PXUIField(DisplayName = "Articul ID", Visibility = PXUIVisibility.Visible, Visible = false, Enabled = false)]
        public virtual int? ArticulID { getset; }
 
        #endregion
 
        #region SubArticulID
        public abstract class subArticulID : IBqlField
        {
        }
 
        [PXDBInt(IsKey = true)]
        [PXUIField(DisplayName = "Sub Articul ID", Visibility = PXUIVisibility.Visible, Visible = false, Enabled = false)]
        public virtual int? SubArticulID { getset; }
 
        #endregion
 
        #region route
        public abstract class subArticulCD : IBqlField
        {
        }
        [PXDBString(50)]
        [PXUIField(DisplayName = "Sub Articul CD", Visibility = PXUIVisibility.Visible)]
        public virtual string SubArticulCD { getset; }
        #endregion
 
        #region route
        public abstract class subArticulName : IBqlField
        {
        }
        [PXDBString(50)]
        [PXUIField(DisplayName = "Sub Articul Name", Visibility = PXUIVisibility.Visible)]
        public virtual string SubArticulName { getset; }
        #endregion
        	
    }

 Next step - create extension class, what are you use. In this example it InventoryItem DAC class:

public class InventoryItemExt : PXCacheExtension<InventoryItem>
   {
       public abstract class usrArticul : IBqlField
       {
       }
 
       [PXDBInt()]
       [PXDefault(PersistingCheck = PXPersistingCheck.Nothing)]
       [PXUIField(DisplayName = "Articul CD")]
       [PXSelector(typeof(Search<UsrArticul.articulID>), SubstituteKey = typeof(UsrArticul.articulCD))]
       public virtual int? UsrArticul { getset; }
 
       public abstract class usrSubArticul : IBqlField
       {
       }
 
       [PXDBInt()]
       [PXSelector(typeof(Search<UsrSubArticuls.subArticulIDWhere<UsrSubArticuls.articulIDEqual<Current<InventoryItemExt.usrArticul>>>>), 
           SubstituteKey = typeof(UsrSubArticuls.subArticulCD))]
       [PXDefault(PersistingCheck = PXPersistingCheck.Nothing)]
       [PXUIField(DisplayName = "Sub Articul CD")]
       public virtual int? UsrSubArticul { getset; }
 
   }

Here I describe two selectors. Overhead and downhead. 

Second selector depend of first. Also pay attention that I used  Equal<Current<InventoryItemExt.usrArticul> (not UsrArticul.articulID), for correct filtering because in this moment when you select first field it is "Current "selector, and you use "YourDacExt.Field".

or in full picture:

[PXSelector(typeof(Search<UsrSubArticuls.subArticulID, Where<UsrSubArticuls.articulID, Equal<Current<InventoryItemExt.usrArticul>>>>), 
           SubstituteKey = typeof(UsrSubArticuls.subArticulCD))]

Staff in bold will allow you to select only sub group.

Do not forget build your project!

After that add controls(PXSelector) to .aspx  view page:

<px:PXSelector CommitChanges="True" ID="usrArticul1" runat="server" DataField="UsrArticul" AllowEdit="True" ></px:PXSelector>
<px:PXSelector CommitChanges="True" ID="usrSubArticul1" runat="server" DataField="UsrSubArticul" AutoRefresh="True" AllowEdit="True" ></px:PXSelector>

After that open this page, and you can see this controls:

 Verify second selector:

No Comments

Add a Comment
 

 

Operator In In Bql

 

Hello everybody,

today I want to write a few words about operator in which was presented in SQL for long ago, but weren't available in Acumatica BQL. But time goes on and now you can use it. For example like this:

Object[] values = new String[] { "BXW000004""BXW000005" };
 
                POOrder item = PXSelect<POOrder,
                    Where<POOrder.orderNbrIn<Required<POOrder.orderNbr>>>>.Select(Base, values);

that code will generate following sql statement:

Select * from POOrder POOrder Where POOrder.OrderNbr In ('BXW000005', 'BXW000004')
	Order by POOrder.OrderNbr

I can say that such approach simplifies some tasks that require dynamic passing of arguments.

No Comments

Add a Comment