Why code changes from dll are not shown on the form in Acumatica

Hello everybody,

today I want to share with you interesting use case which stolen one night of sleep from me, as well as from one of my collegues. 

We had very trivial case. Or at least we thought it is trivial. In Acumatica we had something like this:

#region UsrCardTheme
[PXDBString(256)]
[PXUIField(DisplayName="Card Theme")]
 
public virtual string UsrCardTheme { getset; }
public abstract class usrCardTheme : PX.Data.BQL.BqlString.Field<usrCardTheme> { }
#endregion

For some reason we weren't able to update it in database, so we've made changes to it like this in order to check if we will see it in UI:

#region UsrCardTheme
[PXDBString(256)]
[PXUIField(DisplayName="Card Theme2")]
 
public virtual string UsrCardTheme { getset; }
public abstract class usrCardTheme : PX.Data.BQL.BqlString.Field<usrCardTheme> { }
#endregion

went to the page, open it, and .... label of column remained the same. We've did our best in order to find what is the problem, what's wrong with the syntax, removed completely, restarted IIS, thrown away garbage, nothing worked. 

Until we've found stackoverflow advice to take a look at folder App_RuntimeCode:

and only after we've cleared up folder App_RuntimeCode we've noticed dream of all night, 2 in the end!

How to call non public method of Acumatica

Hello everybody,

today I want to share with you how it's possible to call some methods of Acumatica, which are not public, and which you don't want to copy/paste completely into your source code. In that case reflection will save you. Consider calling of InsertSOAdjustments method of graph SOOrderEntry below.

MethodInfo invokeSOAdjustment = typeof(SOOrderEntry).GetMethod(
    "InsertSOAdjustments"BindingFlags.Instance | BindingFlags.NonPublic, Type.DefaultBinder,
    new[] { typeof(SOOrder), typeof(ARPaymentEntry), typeof(ARPayment) }, null);
 
invokeSOAdjustment.Invoke(Base, new object[] { orderdocgraphpayment });

Also I want to give you a word of warning, that such approach potentially will not be certified, and another way of usage will be the one below:

In extension of SOOrderEntry create lines like those:

[PXOverride]
public void InsertSOAdjustments(SOOrder orderARPaymentEntry docgraphARPayment payment,
    Action<SOOrderARPaymentEntryARPaymentbaseAction)
{
    baseAction(orderdocgraphpayment);
}

and then just call InsertSOAdjustments method whenever you'll have a need for this.

Summary

Because Acumatica is written with C# which is very powerful language which gives you a lot of features you can easily achieve a lot of thigs, also be careful with usage of reflection. Somtime even more then Acumatica team anticipated themselves.

How to avoid navigation away from created item after calling PressSave or Persist action

Hello everybody,

today I want to tell you a story, that swallowed quite big amount of time of whole team.

Recently we've got seemingly easy to fulfil requirement: 

  1. Add button to the grid
  2. Inside of the button fullfil 
    1. Persist
    2. Call to db with modifications
    3. Persist one more time
  3. Leave the page opened on created item in UI

Initially our code looked like this:

public PXAction<SOOrder> SomeAction;
[PXButton(CommitChanges = true)]
[PXUIField(DisplayName = "Some Action", Visible = true)]
protected virtual IEnumerable someAction(PXAdapter adapter)
{
    Base.Actions.PressSave();
    //API call
    Base.Actions.PressSave();
    return adapter.Get();
}

I could say that everything worked perfectly except one tiny detail: after clicking of the button page was navigated away to creation of new Sales order. After plenty of research inside of Acumatica source code we have found this option:

public PXAction<SOOrder> SomeAction;
[PXButton(CommitChanges = true)]
[PXUIField(DisplayName = "Some Action", Visible = true)]
protected virtual IEnumerable someAction(PXAdapter adapter)
{
    Base.Actions.PressSave();
    List<SOOrderresult = new List<SOOrder>();
    //API Call
    result.Add(Base.CurrentDocument.Current);
    return result;
}

Another way of dealing with this bug is this:

public PXAction<SOOrder> SomeAction1;
[PXButton(CommitChanges = true)]
[PXUIField(DisplayName = "Some Action", Visible = true)]
protected virtual IEnumerable btnCreatingNew(PXAdapter adapter)
{
    Base.Actions.PressSave();
    //some other code
    adapter.Searches[adapter.Searches.Length - 1] = Base.CurrentDocument.Current.RefNbr;
    return adapter.Get();
}

Summary

Reason of such behavior is fact that Acumatica uses value returned from action in order to know which order to open. Then happens this:

  1. on the first line of the code your adapter has information that Acumatica should go to <New> sales order
  2. Base.Actions.PressSave() generates sales order and persists it to Db, but doesn't notify adapter about this fact
  3. When adapter.Get is executed, Acumatica reads from it order which should be opened, finds there <New> and navigates away from created SO

In order to deal with you can choose option 1, or option 2. Option 1 I've discovered in Acumatica source code, and option 2 is mentioned at stackoverflow by Ruslan

 

 

 

Template for usage of PXLineNbr attribute in Acumatica

Hello everybody,

today I want to leave a note on how to use PXLineNbr attribute in Acumatica pages. Recently myself and one of my team members struggled a bit with a question on how to add it properly. Below goes a bit changed worked tempalte:

public class PrimaryTable : IBqlTable
{
    #region LineId
    public abstract class lineId : PX.Data.BQL.BqlInt.Field<lineId> { }
 
    [PXDBInt]
    [PXDefault(0)]
    public virtual int? LineId { getset; }
    #endregion
 
    #region LineCntr
    public abstract class lineCntr : PX.Data.BQL.BqlInt.Field<lineCntr> { }
 
    [PXDBInt]
    [PXDefault(0)]
    public virtual int? LineCntr { getset; }
    #endregion
}
 
public class DetailsTable : IBqlTable
{
    #region LineNbr
    public abstract class lineNbr : IBqlField { }
 
    [PXDBInt(IsKey = true)]
    [PXDefault]
    [PXLineNbr(typeof(PrimaryTable.lineCntr))]
    [PXParent(typeof(SelectFrom<PrimaryTable>.Where<PrimaryTable.lineId.IsEqual<PrimaryTable.lineId
        .FromCurrent>>))]
    public virtual int? LineNbr { getset; }
    #endregion
}

Summary

As you see, nothing special in this code. Add PXParent, PXLineNbr and enjoy with line counter feature.

 

How to override BuildTAxRequest method of APInvoiceEntryExternalTax class

Hello everybody,

today I want to leave a short techy post on how to override method BuildTaxRequest of APInvoiceEntryExternalTax class.

public class APInvoiceEntryExternalTaxExtPXGraphExtension<APInvoiceEntryExternalTaxAPInvoiceEntry>
{
    [PXOverride]
    protected virtual GetTaxRequest BuildTaxRequest(APInvoice invoiceFunc<APInvoiceGetTaxRequestbaseAction)
    {
        var result = baseAction(invoice);
        //Your code
 
        return result;
    }
}

Reason for such a behavior is a fact that class APInvoiceEntryExternalTax is graph extension for graph. That's why such a construction is needed.

How to override the Persist method properly

Hello everybody,

today I want to write a few words on how to override persist method of Acumatica properly. When I say properly I mean not save just some fragment of data, but use transaction like approach.

In other words how to achieve all or noghint during persisting.

Quite often I see template like this:

public void Method1()
{
    //normal flow
}
 
string message = "Forbidden to do anything at 18 hour";
public void Method2()
{
    if (DateTime.Now.Hour == 18)
    {
        throw new PXException(message);
    }
}
 
public void RollBackMethod1AndMethod2()
{
 
}
 
 
[PXOverride]
public void Persist(Action del)
{
    try
    {
        del();
        Method1();
        Method2();
    }
    catch (Exception ex)
    {
        if(ex.Message == message)
        {
            RollBackMethod1AndMethod2();
        }
    }

Take a note how it works. Initially it will persist some base data to db, and then executes Method1 and Method2 which throws exception. As usually it is logically to conclude that if Method1 or Method2 generated some or another exception, that it would be good not to persist data into Acumatica. How to achieve this? You can use for this purpose PXTransactionScope. 

Take a look on this sample:

public void Method1()
{
    //normal flow
}
 
string message = "Forbidden to do anything at 18 hour";
public void Method2()
{
    if (DateTime.Now.Hour == 18)
    {
        throw new PXException(message);
    }
}
 
public void RollBackMethod1AndMethod2()
{
 
}
 
 
[PXOverride]
public void Persist(Action del)
{
    using (var ts = new PXTransactionScope())
    {
        del();
        Method1();
        Method2();
    }

As you probably can guess, PXTransactionScope will make sure that either all data remained persisted, or nothing.

Summary

In case if you need to have all or nothing during persistance to database, then feel free to use PXTransactionScope, it will help you to get all or nothing during persitance to database, and also it will help you to avoid adding complicated logic of tracking what was persisted, what wasn't persisted, and how to clean up the data.

Why Acumatica can't restore snapshot bigger then 2Gb?

Hello everybody,

today I want to share with you my guess regarding why Acumatica can't restore snapshot bigger then 2 Gb. As far as I see at database level, all files are going into table UploadFileRevision. If to look into structure of this table, you'll find that it has column data, which looks like this: 

 

if to google a bit, you'll find that maximum size which Varbinary(MAX) can accomodate is 2 Gb. 

That's why if you snapshot is bigger then 2 Gb, look for other way of restoring of your snapshot

Summary

As of now it is my guess on why snapshot can't be restored. I'll update everybody how to deal with this, once I'll find solution, which satisfies me completely.

Accounting terms in Acumatica English <-> Ukrainian

Hello everybody,

I want to make post of Accountancy terms between English and Ukrainian language.

English term Ukrainian term
Account  
Accounting concept  
Accounting entry  
Accounting method  
Accounting record  
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   

Gross income and net income

Hello everybody,

today I want to leave a note on gross income and net income from two standpoints: business and employee.

Gross income = Total sales - Cost of goods sold

Net Income = Total sales - Cost of goods sold - Selling expenses. 

Because Gross income = Total sales - Cost of goods sold quite often Net income is recorded in this form:

Net Income = Gross income - Selling expenses

 

If some business had 1 000 000 $ sales and cost of goods sold is 600 000 $ and selling expenses are 250 000 then

Gross income = 1 000 000 - 600 000 = 400 000

Net Income = 400 000 - 250 000 = 150 000

But if you are an employee, then for you differnet definitions are applied.

Gross income = all salary without any deductions

Net income = what left for employee.

If a person has salary 1000, and 300 of deductions of taxes then his net income is 700 ( his selling expenses considered to be equal zero )

How to implement PXStringListAttribute in Acumatica

Hello,

today I want to leave a post about code quality regarding of PXStringListAttribute. If to look in older versions of Acumatica manuals ( for example T200 manual ) you can see something like this:

public class Something : IBqlTable
{
 
    [PXStringList(
        new string[]
        {
            ShipmentTypes.CollectorNew,
            ShipmentTypes.CollectorSent,
            ShipmentTypes.CollectorResponded,
            ShipmentTypes.CollectorExpired
        },
        new string[]
        {
            "New",
            "Sent",
            "Responded",
            "Expired"
        })]
    [PXString]
    public virtual string Field { getset; }
}

Recommended syntax as of now looks like this:

public class Something : IBqlTable
{
 
    [SurveyResponseStatus.List]
    [PXString]
    public virtual string Field { getset; }
}
 
 
public static class SurveyResponseStatus
{
    public class ListAttribute : PXStringListAttribute
    {
        public ListAttribute() : base(
            new string[] { CollectorNew, CollectorSent, CollectorResponded, CollectorExpired },
            new string[] { Messages.CollectorNew, Messages.CollectorSent, Messages.CollectorResponded, Messages.CollectorExpired })
        { }
    }
 
    public const string CollectorNew = "N";
    public const string CollectorSent = "S";
    public const string CollectorResponded = "R";
    public const string CollectorExpired = "E";
 
    public class CollectorNewStatus : PX.Data.BQL.BqlString.Constant<CollectorNewStatus> { public CollectorNewStatus() : base(CollectorNew) { } }
    public class CollectorSentStatus : PX.Data.BQL.BqlString.Constant<CollectorSentStatus> { public CollectorSentStatus() : base(CollectorSent) { } }
    public class CollectorRespondedStatus : PX.Data.BQL.BqlString.Constant<CollectorRespondedStatus> { public CollectorRespondedStatus() : base(CollectorResponded) { } }
    public class CollectorExpiredStatus : PX.Data.BQL.BqlString.Constant<CollectorExpiredStatus> { public CollectorExpiredStatus() : base(CollectorExpired) { } }
}

and also class Messages looks somehow like this:

[PXLocalizable(Prefix)]
public static class Messages
{

    #region Survey Response Status
 
    public const string CollectorNew = "New";
    public const string CollectorSent = "Sent";
    public const string CollectorResponded = "Responded";
    public const string CollectorExpired = "Expired";

also new code from initial standpoint looks as much bigger amount of lines, but from standpoint of reusability, it's much better. It's easier to decorate any String column, which is much easier to use. Also in your code, you can make comparisons against compiled constant instead of hard coding string values.

Summary

Take your time to kind of memorize, or bookmark new way of using syntax, which will be easier to re-use and maitan.