Narrowing Allowed Values (PXRestrictor)

As a programmer, we love reusable code. It gives us the ability to define a field or method once and rarely look back. Defining a DAC in Acumatica with the use of PXSelector or even some complex attribute resulting in a PXSelector is one of the most common things I do in creating new functionality. Unfortunately, the field definition, to be reusable, must be defined in a way that returns all values that may be needed under any circumstance.

In this week’s efforts, the DAC is defined with PXHidden within my graph for use as a filter. Using PX.Objects.IN.LocationAvailAttribute, my field returns locations. I have enhanced the location to include a condition for the parts stored in the location and a flag to identify the location as NCM. Based on certain business logic, I need to prompt the user for a location that meets certain requirements of condition and NCM flag. For simplicity, the filter DAC contains a LocationType field that I set when calling the smart panel, for which the value will be used to restrict the list of locations returned.

The end of the post includes the complete LocationParamFilter DAC and the static class LocationTypes used to specify which locations will be returned (and validated) by the selector. Note that the base definition of LocationID is:

#region LocationID
[LocationAvail(
	typeof(UsrTag.inventoryID),
	typeof(UsrTag.subItemID),
	typeof(UsrTag.siteID),
	null, null, null
	)]
[PXDefault()]
[PXUIField(DisplayName = Messages.FldToLocationID)]
public virtual int? LocationID { get; set; }
public abstract class locationID
	: PX.Data.BQL.BqlInt.Field<locationID> { }
#endregion

That definition will return all locations. We limit that list by adding PXRestrictor. The following sample will “restrict” the list to only items marked with the NCM flag when the LocationType field of filter is set to NCM.

[PXRestrictor(typeof(
	Where<Current<LocationParamFilter.locationType>,
			NotEqual<LocationTypes.ncm>,
		Or<INLocationExt.usrNcm, Equal<True>>>
		), "")]

Notice the section of the Where clause that effectively reads “Current<LocationParamFilter.locationType> Not Equal NCM”. This will return True and effectively disable the restriction except when the filter’s Location Type has been set to anything other than NCM. The “Or” (takes effect when the Location Type is NCM) says that the NCM flag must be True. In other words, the list of locations – when the filter was set to only show NCM locations – will be be restricted to only Locations flagged as NCM.

Similarly, we can stack many PXRestrictor attributes. Using the “Current<LocationParamFilter.locationType>” portion enables only executing the restriction under the Location Type that I specified. Therefore, I can define the field in the DAC a single time and have it return and validate only locations that fit my specific use case. If none of the restricted LocationTypes is specified, none of the restrictions will be imposed. See the full LocationParamFilter DAC and LocationTypes class below.

#region LocationParamFilter
[PXHidden]
[Serializable()]
public partial class LocationParamFilter : IBqlTable
{
	#region LocationID
	[LocationAvail(
		typeof(UsrTag.inventoryID),
		typeof(UsrTag.subItemID),
		typeof(UsrTag.siteID),
		null, null, null
		)]
	[PXRestrictor(typeof(
		Where<Current<LocationParamFilter.locationType>,
				NotEqual<LocationTypes.ncm>,
			Or<INLocationExt.usrNcm, Equal<True>>>
			), "")]
	[PXRestrictor(typeof(
		Where<Current<LocationParamFilter.locationType>,
				NotEqual<LocationTypes.core>,
			Or<INLocationExt.usrCondition, Equal<ConditionType.core>>>
			), "")]
	[PXRestrictor(typeof(
		Where<Current<LocationParamFilter.locationType>,
				NotEqual<LocationTypes.ncmOrCore>,
			Or<INLocationExt.usrNcm, Equal<True>,
			Or<INLocationExt.usrCondition, Equal<ConditionType.core>>>>
			), "")]
	[PXRestrictor(typeof(
		Where2<
				Where<Current<LocationParamFilter.locationType>,
						NotEqual<LocationTypes.stock>>,
			Or<
				Where<INLocationExt.usrNcm, Equal<False>,
					And<INLocationExt.usrCondition,
						NotEqual<ConditionType.core>>>>>
			), "")]
	[PXDefault()]
	[PXUIField(DisplayName = Messages.FldToLocationID)]
	public virtual int? LocationID { get; set; }
	public abstract class locationID
		: PX.Data.BQL.BqlInt.Field<locationID> { }
	#endregion
	#region LocationType
	[PXDBString(1)]
	[LocationTypes.List]
	public virtual string LocationType { get; set; }
	public abstract class locationType
		: PX.Data.BQL.BqlString.Field<locationType> { }
	#endregion
}
#endregion

public static class LocationTypes
{
	public class ListAttribute : PXStringListAttribute
	{
		public ListAttribute() : base(
			new[]
			{
			Pair(Ncm, "NCM"),
			Pair(Core, "Core"),
			Pair(NcmOrCore, "NCM or Core"),
			Pair(Stock, "Stock"),
			})
		{ }
	}

	public const string Ncm = "N";
	public class ncm
		: PX.Data.BQL.BqlString.Constant<ncm>
			{ public ncm() : base(Ncm) { } }

	public const string Core = "C";
	public class core
		: PX.Data.BQL.BqlString.Constant<core>
			{ public core() : base(Core) { } }

	public const string NcmOrCore = "B";
	public class ncmOrCore
		: PX.Data.BQL.BqlString.Constant<ncmOrCore>
			{ public ncmOrCore() : base(NcmOrCore) { } }

	public const string Stock = "S";
	public class stock
		: PX.Data.BQL.BqlString.Constant<stock>
			{ public stock() : base(Stock) { } }
}

Leave a Reply