PXAggregateAttribute usage for saving of your development time

Hi everybody,

today I want to share one of the insights from code and code, which was conducted by Stéphane Bélanger, and which seems useful, also may be controversial. But still, you may like it. So let me introduce or re-introduce PXAggregateAttribute .

If to sum up, purpose of PXAggregateAttribute, is merging of couple of attributes into single one. Consider following situation. You need to have selector Active customer over multiple places in Acumatica: at Purchase orders form, and Sales order form and at Shipment form. And difference between them will be zero, or close to that. Wouldn't that be nice, to declare this attribute in one place, and then to re-use it everywhere else? Of course yes. And for that purpose, Acumatica introduced attribute PXAggregateAttribute. 

Below goes code sample, of how that can be used, and re-used:

[PXInt()]
[PXUIField(DisplayName = "Active Customer")]
[PXDefault()]
[PXSelector(typeof(Search<BAccountR.bAccountID,
    Where<BAccountR.status, Equal<BAccount.status.active>>>))]
public class ActiveCustomer : PXAggregateAttribute {}
 
 
public class SOShipmentExt : PXCacheExtension<SOShipment>
{
    public abstract class activeCustomer : PX.Data.BQL.BqlInt.Field<activeCustomer> { }
 
    // Acuminator disable once PX1030 PXDefaultIncorrectUse [For demonstration purposes that will be sufficient]
    [ActiveCustomer]
    public Int32? ActiveCustomer { getset; }
}
 
public class POOrderExt : PXCacheExtension<POOrder>
{
    public abstract class activeCustomer : PX.Data.BQL.BqlInt.Field<activeCustomer> { }
 
    // Acuminator disable once PX1030 PXDefaultIncorrectUse [For demonstration purposes that will be sufficient]
    [ActiveCustomer]
    public Int32? ActiveCustomer { getset; }
}
 
public class SOOrderExt : PXCacheExtension<SOOrder>
{
    public abstract class activeCustomer : PX.Data.BQL.BqlInt.Field<activeCustomer> { }
 
    // Acuminator disable once PX1030 PXDefaultIncorrectUse [For demonstration purposes that will be sufficient]
    [ActiveCustomer]
    public Int32? ActiveCustomer { getset; }
}

 As you can see, above our class ActiveCustomer, we've declared bundle of attributes, and then everywhere else, we've used them, but instead of duplication of code, we've re-used them as single line.

 

Change field state dynamically in Acumatica. Or changing field type in Acumatica

Hello friends.

Today I will tell you how we can dynamically change the state of the field in a grid. I want to notice that this way works only for grid and will not work with Form.

In our case we will create DAC Extension for SOLine with 2 fields.

First field will choose the type we want to convert the field into and in the second field will interact with it.

In this example I made fields PXDBString on purpose to show how they are saved to the database.

[PXCacheName(SoLineExtCacheName)]
public class SoLineExt : PXCacheExtension<SOLine>
{
    private const string SoLineExtCacheName = "SoLineExt";
    public static bool IsActive() => true;
 
    #region UsrSlsOrdPrimaryReasonCode
 
    [PXDBString(255)]
    [PXStringList(
        new[] { "1""2""3""4""5" },
        new[] { "DropDown""TextBox""DateTime""CheckBox""Selector" })]
    [PXUIField(DisplayName = "FieldOne")]
    public string UsrFieldOne { getset; }
 
    public abstract class usrFieldOne : BqlString.Field<usrFieldOne>
    {
    }
 
    #endregion
 
    #region UsrSlsOrdSecondaryReasonCode
 
    [PXDBString(255)]
    [PXUIField(DisplayName = "FieldTwo")]
    public string UsrFieldTwo { getset; }
 
    public abstract class usrFieldTwo : BqlString.Field<usrFieldTwo>
    {
    }
 
    #endregion
 
}

 

The next step should be creating the GraphExtension for the graph.

Create a FieldSelecting event for UsrFieldTwo that will dynamically change the state.

We will also check if UsrFieldOne is empty then by default we can create our UsrFieldTwo as a text field.

[PXCacheName(SoOrderEntryExtCacheName)]
public class SOrderEntryExt : PXGraphExtension<SOOrderEntry>
{
    private const string SoOrderEntryExtCacheName = "SoOrderEntryExt";
    public static bool IsActive() => true;
 
    public PXSelect<FixedAsset> FixedAssets; // Here we've a view that we'll show in a selector
 
    protected void _(Events.FieldSelecting<SOLine, SoLineExt.usrFieldTwo> args)
    {
        var fieldOne = args.Row.GetExtension<SoLineExt>()?.UsrFieldOne;
        if (args.Row == null || string.IsNullOrWhiteSpace(fieldOne))
        {
            return;
        }

 

The next step is to define a Switch conditional construct in which we will check the type we should transform UsrFieldTwo into and return to text field.

        switch (fieldOne)
        {
            case "1":
                args.ReturnState = PXStringState.CreateInstance(args.ReturnState, 100, truetypeof(SoLineExt.usrFieldTwo).Name,
                    false, -1, string.Empty, new[] { "val_1""val_2""val_3", }, new[] { "val_1""val_2""val_3", }, falsenull);
                // We can uncomment this line if need to MultiSelect in dropdown.
                //((PXStringState)args.ReturnState).MultiSelect = true;
                break;
            case "2":
                args.ReturnState = PXStringState.CreateInstance(args.ReturnState, 100, null,
                    typeof(SoLineExt.usrFieldTwo).Name, false, -1, nullnullnulltruenull);
                break;
            case "3":
                args.ReturnState = PXDateState.CreateInstance(args.ReturnState, typeof(SoLineExt.usrFieldTwo).Name, false, -1,
                    nullnullnullnull);
                break;
            case "4":
                args.ReturnState = PXFieldState.CreateInstance(args.ReturnState, typeof(bool), falsefalse, -1,
                    nullnullfalsetypeof(SoLineExt.usrFieldTwo).Name, nullnullnull, PXErrorLevel.Undefined, truetrue,
                    null, PXUIVisibility.Visible, nullnullnull); break;
            case "5":
                var state = PXFieldState.CreateInstance(args.ReturnState,
                    typeof(string), falsetrue, 1, nullnullnulltypeof(SoLineExt.usrFieldTwo).Name);
                state.ViewName = nameof(FixedAssets);
                state.DescriptionName = nameof(FixedAsset.description);
                state.FieldList = new[]
                {
                    nameof(FixedAsset.assetID),
                    nameof(FixedAsset.description),
                    nameof(FixedAsset.assetTypeID),
                    nameof(FixedAsset.assetCD)
                };
                var selectorCache = Base.Caches<FixedAsset>();
                state.HeaderList = new[]
                {
                    PXUIFieldAttribute.GetDisplayName<FixedAsset.assetID>(selectorCache),
                    PXUIFieldAttribute.GetDisplayName<FixedAsset.description>(selectorCache),
                    PXUIFieldAttribute.GetDisplayName<FixedAsset.assetTypeID>(selectorCache),
                    PXUIFieldAttribute.GetDisplayName<FixedAsset.assetCD>(selectorCache),
                };
                state.DisplayName = PXUIFieldAttribute.GetDisplayName<SoLineExt.usrFieldTwo>(args.Cache);
                state.Visible = true;
                state.Visibility = PXUIVisibility.Visible;
                state.Enabled = true;
                args.ReturnState = state;
                break;
            default:
                args.ReturnState = PXStringState.CreateInstance(args.ReturnState, 100, null,
                    typeof(SoLineExt.usrFieldTwo).Name, false, -1, nullnullnulltruenull);
                break;
        }
 
    }
}

 After we have prepared our GraphExtension and CacheExtension we need to add our fields to the View, so we can do this through the Customization Editor for clarity.

Very important point! Since we dynamically change the type of our field UsrFieldTwo, you must set MatrixMode="true" for this column:

That's it, now we can make a publish and check it out.

You must remember that all fields that are specified in the list in the database will be stored as a string, so do not forget to convert them to the correct type when you work with them, to avoid problems with the type of ghosting.

If the field is empty then by default it will be a text field.

  • If you select dropdown then we get the value we set in e.ReturnState.

  • The text field is identical to the empty field.

  • If you select CheckBox, our field will have two states True or False.

  • If we select DateTime we can select a date from the DateTimePicker.

 

  • Of course, Selector. This will display the data from our previously defined FixedAsset view.

Let's also see how this data is stored in the database which is demonstrated by sampling.

That's all for now, thank you for your attention, I hope this article will be useful for you

All for successful coding.

 

How to add GI to side panel and Pivot table to side panel

Hello everybody,

Today a want to share with you approach how to add GI to Side Panel, add Pivot Table to Side Panel, and how current row (current field value) of screen bounds with filter of GI and PT.

Also, I will show how to add all custom features with GI and PT to customization package.

As example, we will add side panel to Customers screen and add two actions, first - custom GI report about sales, second – pivot table with information about Sales Order that connected to current customer on screen.

  1. First, we create custom GI report with joins of tables, result grid with columns that we need for pivot table.

Pivot tables in Acumatica are created and based on GI. All columns in ResultGrid tab will able in pivot table.

 

Add Parameter and Conditions for it.

Click button “VIEW INQUIRY” and check how works result and filter:

Create and setup pivot table as we need:

Click “Save as Pivot”, enter name of pivot table, select check box “Shared Configuration”

Then Acumatica show additional tab with Pivot Table as on screen-shot

Setup Rows, Columns and Values for pivot table, then unclick “Edit pivot table” button  

Pivot table will look as on screen-shot. Also you can drag and drop field between Rows, Columns and Values on pivot table result as you need:

Add side panel to Customer screen (AR303000).

First create new customization package and add AR303000 screen to it.

Save new action in customization and publish customization. Check work side panel with GI on Customers screen:

Add Pivot table to side panel.

Acumatica hides tab of GI with pivot table on side panel and we can see only GI report grid on side panel.

But it is opportunity in Acumatica to add pivot table to side panel using dashboard.

Also we will setup dashboard filter and pipeline it with current customer and pivot table.

  • Create custom dashboard and add parameters as on screen-shot:

Click “VIEW” and setup layout of dashboard

Add pivot table to dashboard: select our custom GI report and Pivot Table (required fields):

Click “FILTER SETTINGS”, add a new and setup it:

Click “FINISH” and exit from DESING mode of dashboard.

Add dashboard with pivot table to side panel.

Open customization package, add new action with side panel type to Customer screen. Select our dashboard ass Destination Screen, also setup Navigation Parameters:

Click OK and Publish customization.

Check how works side panel with Pivot Table. Click “Next” on screen and check how pivot table changes data, depends from current customer:

 

How to get started with Acumatica development

Hi everybody,

below goes video, which describes on how to get started with Acumatica development:

In that video you'll find out:

1. How to install specific Acumatica build

2. How to debug C# code written by you in Visual Studio and Customization designer

3. How to get possibility to debug Acumatica source code

4. Default user name and password of Acumatica instnace

And much much more. Please watch and support with your likes!

 

 

Acumatica added RabbitMQ and going to replace MSMQ

Hi everybody,

Today I want to write a few words regarding Acumatica making a decision on adding RabbitMQ.

If you are going to install Acumatica 2022 R2, on one of the steps of installation, you may see this:

As you can figure out from the caption, by default Acumatica now installs RabbitMQ.

 

 

 

Another process has updated the {Table} record. Your changes will be lost

Hello everybody,

Today a want to share with you couple approaches that can help you to fix a famous Acumatica’s error:

“Another process has updated the {Table} record. Your changes will be lost”

You can get this error when you open two the same screens with the same record from DB and then modify data on both screens and click Save.

Or maybe you have custom logic that run some functionality in PXLongOperation (run in multi-threading mode) and one record from DB can be modified by different threads and then during persisting you will get this error.

Or maybe you need to create and to release several documents during code logic that use the same record in DB (for example Create/Release Invoice and then Create/Release Payment that updates ARRegister table).

We get this error when try persist in DB record that was modified before by another graph or process and record has another version (another value) of TimeStamp field.

So, TimeStamp control version in Acumatica and DB is done using field TimeStamp (or Tstamp) with [PXDBTimestamp] attribute in the DAC table. When record is updated, the TimeStamp field increase own value at 1. Every time when a graph persist record to DB, it verifies version of the record to be sure that the record is not changed by another process.

We have three approaches that can help us fix this error and run code logic till the end. (Two first approaches you can find in source code of Acumatica site, Acumatica uses it often):

  1. Use SelectTimeStamp() method of the graph:

When you invoke this methos, graph selects and sets TimeStamp field with new version and then you can persist data to DB.

Here one of examples from Acumatica’s VendorMaint graph, Persist method:

public override void Persist()
{
    using (PXTransactionScope ts = new PXTransactionScope())
    {
        bool persisted = false;
        try
        {
            BAccountRestrictionHelper.Persist();
            persisted = (base.Persist(typeof(Vendor), PXDBOperation.Update) > 0);
        }
        catch
        {
            Caches[typeof(Vendor)].Persisted(true);
            throw;
        }
        base.Persist();
        if (persisted)
        {
            base.SelectTimeStamp();
        }
        ts.Complete();
    }
}

 

Another example:

private void PrepareForPOCreate(List<SOLine> listSO)
{
    foreach (SOLine item in listSO)
    {
        item.Qty = item.GetExtension<APSOLineExt>().UsrMasterSOQty;
 
        var inventoryItem = UpdatePOOrderExtension.CheckDefVendor(this.Base, item.InventoryID);
 
        if (inventoryItem == null)
        {
            Base.Caches<SOLine>().SetValueExt<SOLine.pOCreate>(item, false);
            Base.Transactions.Cache.Update(item);
        }
    }
 
    Base.SelectTimeStamp();
    Base.Save.Press();
}

 2. Use PXTimeStampScope.SetRecordComesFirst() method:

Sometimes first approach doesn’t help and error still is raised.

 By default, Acumatica checks TimeStamp version of graph only when key fields are modified. But SetRecordComesFirst() method activates RecordComesFirst flag and Acumatica check TimeStamp version for all changes in cache.

Here is example how to create and release Payment:

private static ARPayment CreateAndReleasePayment(ARInvoice arInvoice)
 {
     ARPaymentEntry paymentEntry = PXGraph.CreateInstance<ARPaymentEntry>();
 
     var arAdjust = SelectFrom<ARAdjust>.Where<ARAdjust.adjdRefNbr.IsEqual<@P.AsString>>.View.Select(paymentEntry, arInvoice.RefNbr)?.TopFirst;
 
     if (arAdjust?.AdjgRefNbr == null)
     {
         paymentEntry.CreatePayment(arInvoice, null, arInvoice.DocDate, arInvoice.FinPeriodID, false);
 
         paymentEntry.Document.Current.ExtRefNbr = arInvoice.DocDesc;
         paymentEntry.Document.Current.BranchID = arInvoice.BranchID;
         paymentEntry.Document.UpdateCurrent();
         paymentEntry.Save.Press();
     }
     else
     {
         paymentEntry.Document.Current = SelectFrom<ARPayment>.Where<ARPayment.refNbr.IsEqual<@P.AsString>>.View.Select(paymentEntry, arAdjust.AdjgRefNbr)?.TopFirst;
     }
 
     ARPayment arPayment = paymentEntry.Document.Current;
 
     paymentEntry.Clear();
 
     ARRegister doc = arPayment;
     List<ARRegisterlist = new List<ARRegister>() { doc };
 
     using (new PXTimeStampScope(null))
     {
         PXTimeStampScope.SetRecordComesFirst(typeof(ARRegister), true);
 
         try
         {
             ARDocumentRelease.ReleaseDoc(list, false);
         }
         catch (PXException e)
         {
             PXTrace.WriteError(e.Message);
         }
     }
 
     return arPayment;
 }

Acumatica’s usage of this method in ARInovoiceEntry:

public void ReleaseProcess(List<ARRegister> list)
        {
            PXTimeStampScope.SetRecordComesFirst(typeof(ARInvoice), true);
 
            ARDocumentRelease.ReleaseDoc(list, falsenull, (ab) => { });
        }

 3. Use PXTimeStampScope.DuplicatePersisted() method:

 

The most difficult approach, but helps and fixes the error, if first and second approaches don’t work. There are no examples in Acumatica’s site source code, but below I use it two times (override persist methods):

Lazy is not necessary to use, it is just for performance, because I had long and heavy logic of process on my custom processing screen.

public class ARPaymentEntryExt : PXGraphExtension<ARPaymentEntry>
{
    public static bool IsActive() => true;
 
    [PXOverride]
    public virtual void Persist(Action baseMethod)
    {
        var updInvoices = Base.ARInvoice_DocType_RefNbr.Cache.Updated.Cast<ARInvoice>().ToList();
        var updPayments = Base.Document.Cache.Updated.Cast<ARPayment>().ToList();
 
        baseMethod?.Invoke();
 
        var lazyTempGraph = new Lazy<PXGraph>(() => PXGraph.CreateInstance<PXGraph>());
        foreach (ARInvoice updInvoice in updInvoices)
        {
            PXTimeStampScope.DuplicatePersisted(lazyTempGraph.Value.Caches[typeof(ARRegister)], updInvoice, typeof(ARInvoice));
            PXTimeStampScope.DuplicatePersisted(lazyTempGraph.Value.Caches[typeof(ARPayment)], updInvoice, typeof(ARInvoice));
        }
        foreach (ARPayment updPayment in updPayments)
        {
            PXTimeStampScope.DuplicatePersisted(lazyTempGraph.Value.Caches[typeof(ARRegister)], updPayment, typeof(ARPayment));
        }
    }
}
public class ARDocumentReleaseExt : PXGraphExtension<ARReleaseProcess>
{
    public static bool IsActive() => true;
 
    [PXOverride]
    public virtual void Persist(Action baseMethod)
    {
        var updRegisters = Base.ARDocument.Cache.Updated.Cast<ARRegister>().ToList();
        baseMethod?.Invoke();
 
        var lazyTempGraph = new Lazy<PXGraph>(() => PXGraph.CreateInstance<PXGraph>());
 
        foreach (ARRegister updRegister in updRegisters)
        {
            PXTimeStampScope.DuplicatePersisted(lazyTempGraph.Value.Caches[typeof(ARPayment)], updRegister, typeof(ARRegister));
            PXTimeStampScope.DuplicatePersisted(lazyTempGraph.Value.Caches[typeof(ARInvoice)], updRegister, typeof(ARRegister));
        }
    }
}

 Summary

As you can see, other process has updated is kind of commonly seen error in Acumatica, and you now seen at least three ways of how to handle that

Acumatica requirements for development

Hi everybody,

today I want to leave a short note regarding of what is needed, in order to be able to develop for Acumatica ERP.

REQUIREMENTS FOR DEV MACHINE

Display resolution: Minimum 1024 × 768, Typical 1920×1080

Adobe Reader: (to open Acumatica ERP PDF documents) 2019 or later

Microsoft Office: (to view documents exported from Acumatica ERP)

  • MS Office 2019
  • MS Office 2016
  • MS Office 2013
  • MS Office 2010
  • MS Office 2007
  • MS Office 2003 with the Microsoft Office 2007 compatibility pack

IIS

Web Browsers:

  • Microsoft Edge 44 or later
  • Mozilla Firefox 82 or later
  • Apple Safari 12 or later
  • Google Chrome 87 or later

As of June 15, 2022, Microsoft Internet Explorer is no longer supported by any version of Acumatica ERP as the browser has been retired by Microsoft who now recommends Microsoft Edge.

DATABASE REQUIREMENTS

Microsoft SQL Server: 2019, 2017, or 2016

MySQL Community Edition Server: 5.7 and 8.0 64-bit edition

MariaDB: Version 10

Memory: 8 GB RAM

CPU: 2 cores; 2 GHz

Hard Disk Space: For each database, 1 GB available hard disk space. Depending on the number of transactions, additional hard disk space may be required to store large numbers of transactions.

CODE AUTHORING ENVIRONMENTS

To create stand-alone applications with Acumatica ERP or develop customizations and add-on solutions on top of Acumatica ERP, you need one of the integrated development environments (IDEs) listed below.

Operating System

  • Windows 10
  • Windows Server 2019
  • Windows Server 2022

Microsoft Visual Studio with Microsoft Web Developer Tools:

  • 20xx: Community, Professional, and Enterprise editions ( xx stands for version numbers, 09, ..., 19, 22 )
  • (OR) Rider 

Summary

If to sum upp, if you want to develop Acumatica, you'll need Windows, IIS, Database and Visual Studio or Rider

How to remove validation of the Lot/Serial Class field on the Stock Items page

Today I want to share with you the article "How to remove the validation of the Lot/Serial Class field on the Stock Items page".

Recently I had a case where I wanted to change Lot/Serial Class at any time regardless of its use, but out of the box, Acumatica doesn't give this possibility and show an error or warning "Lot/serial class cannot be changed when its tracking method as it is not compatible with the previous class and the item is in use" as it is demonstrated in the image below.

There is a possibility of this warning

The red error comes from validation on FieldVerifying, but the yellow comes from the rule in INItemPlan.inventoryID.InventoryLotSerClassIDRule. Although looks like a warning, it reverts your change so it doesn't let you to change it.

The solution of the Lot/serial class can be changed as it is not a complicated process but can be quite time-consuming.

First of all we need to create a GraphExtension where by overriding the Initialize we provide ability to change the Lot/Serial class.

And we also need to override the FieldVerifiyng event and not call the base method so that this field is not validated.

The full code is here:

[PXCacheName(InventoryItemMaintExtCacheName)]
public class InventoryItemMaintExt : PXGraphExtension<InventoryItemMaint>
{
	private const string InventoryItemMaintExtCacheName = "InventoryItemMaintExt";
	public static bool IsActive() => true;
 
	public override void Initialize()
	{
		base.Initialize();
		Base.MakeRuleWeakeningScopeFor<InventoryItem.lotSerClassID>(RuleWeakenLevel.AllowEdit);
	}
 
	protected virtual void _(Events.FieldVerifying<InventoryItem,
	InventoryItem.lotSerClassID> e, PXFieldVerifying baseMethod)
	{
		//baseMethod?.Invoke(sender, e); 
		//skip the baseMethod so Messages.ItemLotSerClassVerifying is not thrown
	}
}

 And final word, use with caution. Because you may influence plenty of other pages in Acumatica.

 

 

 

How to customize PXDefault attribute of Acumatica

Sometimes you can get request from BA or client to add new field like this: “On the Customers (AR.30.30.00) screen add a checkbox field called Membership. It is a required field and the default value is False. The Membership field should be already there.”

How we usually develop it:

public class PACustomerExt : PXCacheExtension<Customer>
{
	public static bool IsActive() => true;
 
	#region UsrMembership
	[PXDBBool]
	[PXUIField(DisplayName = "Membership", Required = true)]
	[PXDefault(false)]
	public virtual bool? UsrMembership { getset; }
	public abstract class usrMembership : PX.Data.BQL.BqlBool.Field<usrMembership> { }
	#endregion
}

 

And as you know, this logic will work correct for new records in DB, but when you will change existing records in DB, than you will get nullable Exception because of this custom field and PXDefault attribute. “PersistingCheck = PXPersistingCkeck.Nothing” do not help, it excludes the required status of the custom field.

So we can fix this issue and develop custom Default attribute, and we do not need create graph extension and develop additional logic in events.

Here is example of correct logic according the request:

public class PACustomerExt : PXCacheExtension<Customer>
{
	public static bool IsActive() => true;
 
	#region UsrMembership
	[PXDBBool]
	[PXUIField(DisplayName = "Membership", Required = true)]
	[PXDefaultCustom(false)]
	public virtual bool? UsrMembership { getset; }
	public abstract class usrMembership : PX.Data.BQL.BqlBool.Field<usrMembership> { }
	#endregion
}
 
 
[PXAttributeFamily(typeof(PXDefaultAttribute))]
public class PXDefaultCustomAttribute : PXDefaultAttribute
{
	public PXDefaultCustomAttribute(object value) : base(value) { }
 
 
	public override void RowPersisting(PXCache sender, PXRowPersistingEventArgs e)
	{
		var fieldValue = sender.GetValue(e.Row, base.FieldName);
 
		if (fieldValue == null)
			sender.SetValue(e.Row, base.FieldName, false);
	}
}

 

Also you can customize PXDefault attribute with any logic that you need, one more example:

[PXAttributeFamily(typeof(PXDefaultAttribute))]
public class PXDefaultCustomAttribute : PXDefaultAttribute
{
	public PXDefaultCustomAttribute(object value) : base(value) { }
 
	public override void FieldDefaulting(PXCache sender, PXFieldDefaultingEventArgs e)
	{
		base.FieldDefaulting(sender, e); // you can raise base event and logic 
 
		e.NewValue = base._Constant;  // you can setup or check constant value of default attribute
 
		// develop needed logic
	}
 
	public override void RowPersisting(PXCache sender, PXRowPersistingEventArgs e)
	{
		// develop needed logic before value will be persisted to DB
 
		var fieldValue = sender.GetValue(e.Row, base.FieldName);
 
		if (fieldValue == null)
			sender.SetValue(e.Row, base.FieldName, false);
 
		// base.RowPersisting(sender, e);  - you can raise base event if you need, also you can set up PXPersistingCheck on you custom attribute
	}
}

 

How to use LoadOnDemand in PXSmartPanel

First of all I want to say that if you do not use "PXTabItem" in your "PXSmartPanel" you should not set LoadOnDemand(by default this attribute has "False" value).

In our example I will show you the difference between using this attribute for "PXSmartPanel" with "True" or "False" values. For that I prepared a short example:

We will use two TabItems (Stock Items for the first and Non-Stock Items for the second).

Let's create them in GraphExt.

public SelectFrom<InventoryItem>.Where<InventoryItem.stkItem.IsEqual<True>>.View StockItemView;
public SelectFrom<InventoryItem>.Where<InventoryItem.stkItem.IsEqual<False>>.View NonStockItemView;

 Let's add button to GraphExt and to the View. For this example we will use the Sales Orders page and add PXSmartPanel to the View.

public sealed class SoOrderEntryExt : PXGraphExtension<SOOrderEntry>
{
    public static bool IsActive() => true;
 
    public PXSelect<SOLine> MyPanelView;
 
    public PXAction<SOOrder> noteAction;
    [PXUIField(DisplayName = "TestNoteButton", MapViewRights = PXCacheRights.Select, MapEnableRights = PXCacheRights.Update)]
    [PXButton(ImageKey = PX.Web.UI.Sprite.Main.DataEntryF)]
    protected IEnumerable NoteAction(PXAdapter adapter)
    {
        if (Base.Transactions.Current != null &&
            MyPanelView.AskExt() == WebDialogResult.OK)
        {
            //extra stuff here if needed when OK is pushed
        }
 
        return adapter.Get();
    }
 
    public SelectFrom<InventoryItem>.Where<InventoryItem.stkItem.IsEqual<True>>.View StockItemView;
    public SelectFrom<InventoryItem>.Where<InventoryItem.stkItem.IsEqual<False>>.View NonStockItemView;
}

 

ASPX. PXSmartPanel:

<px:PXSmartPanel runat="server" ID="PXSmartPanelNote" DesignView="Hidden" LoadOnDemand="false" CreateOnDemand="false" 
                     CaptionVisible="true" Caption="Order Notes" Key="MyPanelView">
        <px:PXTab ID="PXTab123" runat="server" Height="540px" Style="z-index100;" Width="100%">
            <Items>
                <px:PXTabItem Text="NonStockItem" >
                    <Template>
                        <px:PXGrid runat="server" ID="CstPXGrid4" Width="100%" DataSourceID="ds" SyncPosition="True">
                            <Levels>
                                <px:PXGridLevel DataMember="NonStockItemView">
                                    <Columns>
                                        <px:PXGridColumn DataField="StkItem" Width="60" />
                                        <px:PXGridColumn DataField="InventoryCD" Width="70" />
                                    </Columns>
                                </px:PXGridLevel>
                            </Levels>
                        </px:PXGrid>
                    </Template>
                </px:PXTabItem>
                <px:PXTabItem Text="StockItem">
                    <Template>
                        <px:PXGrid runat="server" ID="CstPXGrid5" Width="100%" SyncPosition="True" DataSourceID="ds">
                            <Levels>
                                <px:PXGridLevel DataMember="StockItemView">
                                    <Columns>
                                        <px:PXGridColumn DataField="StkItem" Width="60" />
                                        <px:PXGridColumn DataField="InventoryCD" Width="70" />
                                    </Columns>
                                </px:PXGridLevel>
                            </Levels>
                        </px:PXGrid>
                    </Template>
                </px:PXTabItem>
            </Items>
        </px:PXTab>
        <px:PXPanel runat="server" ID="PXPanel12" SkinID="Buttons">
            <px:PXButton runat="server" ID="btnMyNoteOk" Text="OK" DialogResult="OK" />
        </px:PXPanel>
    </px:PXSmartPanel>

 ASPX.  Callback:

<CallbackCommands>
    <px:PXDSCallbackCommand CommitChanges="true" Name="NoteAction" Visible="False" DependOnGrid="grid" />
</CallbackCommands> 

ASPX. Action Bar:

<px:PXToolBarButton Text="TestNoteButton" DependOnGrid="grid">
    <AutoCallBack Command="NoteAction" Target="ds" />
</px:PXToolBarButton>