How can I perform Acumatica BQL JOINs in PXSelector? - c#

I have a Selector field where I want to show 5 columns from 3 different table. They are
The Item, item desctiption, item class, item class description and the default warehouse.
I have found the DACs and the fields.
InventoryItem.inventoryID;
InventoryItem.descr;
INItemClass.itemClassID;
INItemClass.descr;
INItemSite.siteID;
I have also written the [PXSelector] attribute containing the JOIN.
#region Field of Me
public abstract class fieldOfMe : BqlString.Field<fieldOfMe> { }
[PXUIField(DisplayName = "Field Of Me")]
[PXSelector(typeof(
Search2<InventoryItem.inventoryID,
LeftJoin<INItemClass,
On<INItemClass.itemClassID, Equal<InventoryItem.itemClassID>>,
LeftJoin<INItemSite,
On<INItemSite.inventoryID, Equal<InventoryItem.dfltSiteID>>>>>),
typeof(InventoryItem.inventoryCD),
typeof(InventoryItem.descr),
typeof(INItemClass.itemClassCD),
typeof(INItemClass.descr),
typeof(INItemSite.siteID),
ValidateValue = false
)]
public string FieldOfMe { get; set; }
#endregion
This is the screen.
I want to learn the way I can find the default Warehouse. How can I edit the code to see the active warehouse name? I want the one with checkbox.
I have done the biggest part of the task but I still need some help to finish this.

I have modified your select query to do what you want.
Note that in later versions of Acumatica the dfltSiteID field in InventoryItem table will become obsolete and removed. InventoryItemCurySettings.dfltSiteID should be used instead. I have added a second query to demonstrate the use of InventoryItemCurySettings.
#region Field of Me
public abstract class fieldOfMe : BqlString.Field<fieldOfMe> { }
[PXUIField(DisplayName = "Field Of Me")]
[PXSelector(typeof(
Search2<InventoryItem.inventoryID,
InnerJoin<INItemClass,
On<INItemClass.itemClassID, Equal<InventoryItem.itemClassID>>,
LeftJoin<INSite,
On<InventoryItem.dfltSiteID, Equal<INSite.siteID>>>>>),
typeof(InventoryItem.inventoryCD),
typeof(InventoryItem.descr),
typeof(INItemClass.itemClassCD),
typeof(INItemClass.descr),
typeof(INSite.siteCD),
ValidateValue = false
)]
public string FieldOfMe { get; set; }
#endregion
Using InventoryItemCurySettings.dfltSiteID
#region Field of Me2
public abstract class fieldOfMe2 : BqlString.Field<fieldOfMe2> { }
[PXUIField(DisplayName = "Field Of Me2")]
[PXSelector(typeof(
Search2<InventoryItem.inventoryID,
InnerJoin<INItemClass,
On<InventoryItem.itemClassID, Equal<INItemClass.itemClassID>>,
LeftJoin<InventoryItemCurySettings,
On<InventoryItem.inventoryID, Equal<InventoryItemCurySettings.inventoryID>,
And<InventoryItemCurySettings.curyID, Equal<Current<AccessInfo.baseCuryID>>>>,
LeftJoin<INSite,
On<InventoryItemCurySettings.dfltSiteID, Equal<INSite.siteID>>>>>>),
typeof(InventoryItem.inventoryCD),
typeof(InventoryItem.descr),
typeof(INItemClass.itemClassCD),
typeof(INItemClass.descr),
typeof(INSite.siteCD),
ValidateValue = false
)]
public string FieldOfMe2 { get; set; }
#endregion

Related

Can I use PXDBScalar in a DAC Extension to query records from the same table?

I'm very new to the Acumatica world. The task I've been given is to add a field to SOLine that will show a sum of the quantity to be shipped within the next 30 days for the same inventory item on the given SOLine. The request was to have the field be available on a General Inquiry.
I've created a DAC Extension and figured that a field with the PXDBScalar should do it.
However, when I add the field to a Generic Inquiry, I get the same value for all of the records displayed. It's as if when trying to query the same table it is using just one of the item's aggregate value instead of recalculating for each item/row displayed in the GI.
Is there something that I've done incorrectly in the PXDBScalar formula? Am I missing a reference to the current record?
namespace PX.Objects.SO
{
public class SOLineExt : PXCacheExtension<PX.Objects.SO.SOLine>
{
#region DaysOf
public class int_DaysInFuture : PX.Data.BQL.BqlInt.Constant<int_DaysInFuture>
{
public int_DaysInFuture()
: base(30)
{
}
}
#endregion
#region UsrSalesInNextThirtyDays
public abstract class usrSalesInNextThirtyDays : PX.Data.IBqlField {}
[PXDecimal(2)]
[PXUIField(DisplayName="Sales in next 30 days")]
[PXDBScalar(typeof(Search4<SOLine.openQty,
Where<SOLine.inventoryID, Equal<SOLine.inventoryID>
,And< Where<DateDiff<SOLine.shipDate, Today, DateDiff.day>, LessEqual< int_DaysInFuture > >>
>
,Aggregate<GroupBy<SOLine.inventoryID, Sum<SOLine.openQty>>>
>))]
public virtual Decimal? UsrSalesInNextThirtyDays{ get; set; }
#endregion
}
}
So adding this field shows that the linking to InventoryItem was working as expected.
#region UsrSalesInNextThirtyDays2
public abstract class usrSalesInNextThirtyDaysTwo : PX.Data.IBqlField {}
[PXDecimal(2)]
[PXUIField(DisplayName="Base Price")]
[PXDBScalar(typeof(Search<InventoryItem.basePrice,
Where<InventoryItem.inventoryID, Equal<SOLine.inventoryID>>
>))]
public virtual Decimal? UsrSalesInNextThirtyDaysTwo{ get; set; }
#endregion
So I tried adding an inner join to force the line to InventoryItem using the inventoryID field but the resulting query is still returning the sum of the last item returned as the sum result for all records in the result set.
public abstract class usrSalesInNextThirtyDays : PX.Data.IBqlField {}
[PXDecimal(2)]
[PXUIField(DisplayName="Sales in next 30 days")]
[PXDBScalar(typeof(
Search5<SOLine.openQty,
InnerJoin<InventoryItem,
On<SOLine.inventoryID, Equal<InventoryItem.inventoryID>>>,
Where<DateDiff<SOLine.shipDate, Today, DateDiff.day>, LessEqual< int_DaysInFuture > >,
Aggregate<Sum<SOLine.openQty>>
>))]
public virtual Decimal? UsrSalesInNextThirtyDays{ get; set; }
I feel like I'm getting closer to forcing the DAC to generate the correct query but I'm not there yet.
You cannot reference the table in a circular manner this way because of how the sub-queries are generated.

Acumatica - Show / hide custom data fields on the fly via code in events

I have two custom fields, one is a drop down list, and the other is a text box, one of the items in the drop down list is "Other", I wanted to show the custom field only if "Other" is selected, I added an event handler on the FieldUpdated event for the drop down control, and got the following code:
protected void CROpportunity_MyCheckbox_FieldUpdated(PXCache cache, PXFieldUpdatedEventArgs e)
{
var row = (CROpportunity)e.Row;
}
Let's assume the first field name is UsrComb, and second as UsrText.
How can I show/hide other controls from this method?
It is a very similar situation to: Acumatica - FieldUpdated - Enable / Disable another control when a checkbox is ticked / un-ticked
For this, I would build a StringList class to define your list and values, and then use attributes to control the behavior.
First, the List class (combs)
public class Combs
{
//build string list attribute
public class ListAttribute : PXStringListAttribute
{
public ListAttribute() : base(new[]
{
Pair(Comb, "Comb"),
Pair(Other, "Other")
})
{ }
}
//declare constant values
public const string Comb = "Comb";
public const string Other = "Other";
//build constant for values you want to compare in BQL
public class other : PX.Data.BQL.BqlString.Constant<other>
{
public other() : base(Other) {; }
}
}
Next, are the properties/attributes:
#region UsrComb
[PXDBString]
[Combs.List]
[PXUIField(DisplayName = "Comb")]
public virtual string UsrComb { get; set; }
public abstract class usrComb : PX.Data.BQL.BqlBool.Field<usrComb> { }
#endregion
#region UsrText
[PXDBString]
[PXUIField(DisplayName = "Text")]
//pxdefault is required if you have a PXUIRequired attribute.
[PXDefault(PersistingCheck = PXPersistingCheck.Nothing)]
[PXUIRequired(typeof(Where<usrComb, Equal<Combs.other>>))]
[PXUIVisible(typeof(Where<usrComb, Equal<Combs.other>>))]
[PXUIEnabled(typeof(Where<usrComb, Equal<Combs.other>>))]
public virtual String UsrText { get; set; }
public abstract class usrText : PX.Data.BQL.BqlString.Field<usrText> { }
#endregion
PXUIRequired/Enabled/Visible attributes then check to see if the list equals the constant. You can use any combination of the values.
Put your fields on a screen, and ensure UsrComb has CommitChanges set to True. You will change the value to Other and it will then show the text field, and hide it when set back.
If you wish to blank the value after the dropdown is changed away from other, you can use the following code:
protected virtual void CROpportunity_UsrComb_FieldUpdating(PXCache sender, PXFieldUpdatingEventArgs e)
{
if (e.NewValue == null)
return;
string Comb = (string)e.NewValue;
if (Comb != Combs.Other)
sender.SetValueExt<CROpportunityExt.usrText>(e.Row, "");
}

Acumatica - Different PXUIField names for the same field in the same graph for different tabs

I have a page containing several tabs, with the common graph. The problem is that I use one particular field in two tabs and what if the customer needs to display them with different names on each tab?
Here's my DAC:
[PXDBString(15, IsUnicode = true)]
[PXDefault()]
[PXUIField(DisplayName = "Product Foo")]
public virtual string ProductFoo
{
get
{
return this._ProductFoo;
}
set
{
this._ProductFoo = value;
}
}
More particularly, what if I need to name this field not "Product Foo" but "Product Bar" on the second tab?
I can't do it with
protected void Product_ProductFoo_CacheAttached(PXCache sender){}
because as I've already mentioned, they use the common graph.
So is there any solution?
You can if you create an additional DAC for each tab data view such as
[Serializable]
public class Product2 : Product
{
public new abstract class productFoo: PX.Data.IBqlField
{
}
[PXDBString(15, IsUnicode = true)]
[PXDefault()]
[PXUIField(DisplayName = "Product Foo 2")]
public override string ProductFoo
{
get
{
return this._ProductFoo;
}
set
{
this._ProductFoo = value;
}
}
}
You would have to update your view to use Product2, Product3, etc. for each addtional DAC class used for each tab.

Auto Generate Number in Acumatica

I want to create an auto generate number which similar with auto increment for segment ID in Lot/Serial Classes, like in this picture.
Lot/Serial Classes
After I check the code, I notice that it uses PXLineNbr
public abstract class segmentID : PX.Data.IBqlField
{
}
protected Int16? _SegmentID;
[PXDBShort(IsKey = true)]
[PXUIField(DisplayName="Segment Number", Enabled=false)]
[PXLineNbr(typeof(INLotSerClass))]
[PXDefault()]
public virtual Int16? SegmentID
{
get
{
return this._SegmentID;
}
set
{
this._SegmentID = value;
}
}
After I try and apply it in my code, the auto generated number doesn't appear. So I was wandering if I miss something else. Thank you in advance
The pattern I'm using for PXLineNbr is to declare a line number counter field in the master table and a line number field in the detail table. It's simple and it works. LineNbr value is computed automatically from the counter by PXLineNbr attribute.
The LineCntr field:
public class MasterDAC : IBqlTable
{
#region LineCntr
public abstract class LineCntr : IBqlField { }
[PXDBInt]
[PXDefault(0)]
public virtual int? LineCntr { get; set; }
#endregion
}
The LineNbr field:
public class DetailDAC : IBqlTable
{
#region LineNbr
public abstract class lineNbr : IBqlField { }
[PXDBInt(IsKey = true)]
[PXDefault]
[PXLineNbr(typeof(MasterDAC.LineCntr))]
public virtual int? LineNbr { get; set; }
#endregion
}
Have you checked Example 7.1: Numbering Detail Data Records in the T200 training available at Acumatica Open University? It explains in detail, how the PXLineNbr attribute should be used to automatically number detail data records.

How to extend mef using custom attributes?

Is it possible to add some additional attributes to my components which are then set/hydrated using some custom logic/perhaps from a data store? Similar to adding some custom builder strategy in cab/unity ?
UPDATE
e.g.
assuming a class has these properties
[MyImport] string name1 { get; set }
[MyImport] MyType name2 { get; set }
[MyGuid] Guid { get; set; }
with custom attributes MyImport and MyGuid which are resolved by an "extension" to MEF ( which gets executed after the [imports] are resolved ) and has code along these lines
// property SET
var valu = myDBStore.GetValue( instanceGUID, propertyInfo.Name);
propertyInfo.SetValue( instance, TypeDescripter.GetConverter(valu).ConvertTo(propertyType), null);
// property GET - for example only, used during dehydration outside of MEF !
var valu = propertyInfo.GetValue( instance, null);
myDBStore.SetValue( instanceGUID, propertyInfo.Name, TypeDescripter.GetConverter(valu).ConvertTo(typeof(string));
// the above is pseudo code only, pls no comments on correct args/syntax :)
EDIT
components which are then set/hydrated using some custom logic/perhaps from a data store
One can do this via an "ExportFactory".
// "ExportFactory"
public sealed class DataStoreProvider
{
[Export(typeof(Model))]
public Model Item
{
get
{
return [custom logic];
}
}
}
public class NeedsModel
{
[Import(typeof(Model))]
public Model Item { get; set; }
}
Initial Answer
This is possible through MEF's Lazy<T, TMetadata>.
public interface ISomeMetadata
{
string UsefulInfo { get; }
}
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple=false)]
public class ExportBaseAttribute : ExportAttribute, ISomeMetadata
{
public ExportBaseAttribute(string usefulInfo)
:base(typeof(BaseExport))
{
UsefulInfo = usefulInfo;
}
public string UsefulInfo { get; private set; }
}
// BaseExport class is not needed.. just showing advanced attribute usage.
public abstract class BaseExport { }
[ExportBase("Useful Filter Information")]
public class SomeExport : BaseExport
{
}
Then, in your host (composer), you can
[ImportMany(typeof(BaseExport))]
Lazy<BaseExport, ISomeMetadata>[] _baseExports
After you compose, you can run a LINQ filter using .Metadata
var goodExports = from export in _baseExports
where export.Metadata.UsefulInfo ...
select export;

Categories

Resources