I'm extending the AccountByPeriodEnq logic, I just want to add a button on top of my screen to modify the selected GL records but it just doesnt want to show up and I can't figure out why.
Here's my code :
namespace PX.Objects.GL
{
class AccountByPeriodEnqExtensions : PXGraphExtension<AccountByPeriodEnq>
{
#region Actions
public PXAction<AccountByPeriodFilter> Letter;
[PXUIField(Visible = true, DisplayName = "Lettrer")]
[PXButton(CommitChanges = true)]
protected virtual IEnumerable letter(PXAdapter adapter)
{
IReadOnlyCollection<GLTranR> selectedTrans = GetSelectedTrans();
if (selectedTrans.Any())
{
PXLongOperation.StartOperation(this, delegate ()
{
foreach(GLTranR line in selectedTrans)
{
// UpdateSomeFieldsAndPersists
}
});
}
else
{
throw new PXException("Error");
}
return Base.Filter.Select();
}
#endregion
#region Utility
private IReadOnlyCollection<GLTranR> GetSelectedTrans()
{
return Base.GLTranEnq.Cache.Updated
.Cast<GLTranR>()
.Where(tran => tran.Selected == true)
.ToArray();
}
#endregion
}
}
Is there anything I'm missing here ?
Regards,
Edit:
To clarify i'm trying to customize the GL404000, Account Details. And using the inspector I saw the Business logic is in the AccountByPeriodEnq Graph
Using Acumatica Inspect Element feature notice that the 'Account by Period' screen (GL402000) is not using the 'AccountByPeriodEnq' graph.
It is using the 'AccountHistoryByYearEnq' graph instead so that's the graph you want to target:
You also need to declare Actions on the primary DAC of that Graph.
The one for 'AccountHistoryByYearEnq' is a little bit harder to find than usual.
You can use Acumatica Source Code page and search for 'PXPrimaryGraph(typeof(AccountHistoryByYearEnq)':
In this case it is AccountByYearFilter DAC that you should use:
[System.SerializableAttribute()]
[PXCacheName(Messages.Account)]
[PXPrimaryGraph(typeof(AccountHistoryByYearEnq), Filter = typeof(AccountByYearFilter))]
public partial class Account : PX.Data.IBqlTable, PX.SM.IIncludable
{
[…]
}
I think this is a special case for Filter, when there's no filter Account would have been the DAC to use for the Actions.
Now that you have identified the Primary Graph of the Screen (AccountHistoryByYearEnq) and the Primary DAC of the Graph (AccountByYearFilter) it should work as expected:
public class AccountByPeriodEnq_Extension : PXGraphExtension<AccountHistoryByYearEnq>
{
public PXAction<AccountByYearFilter> letter;
[PXUIField(DisplayName = "Letter")]
[PXButton]
protected virtual IEnumerable Letter(PXAdapter adapter)
{
return adapter.Get();
}
}
UI:
EDIT:
For Account detail page (GL404000), use the same code with different DAC and Graph:
using PX.Data;
using System.Collections;
namespace PX.Objects.GL
{
public class AccountByPeriodEnq_Extension : PXGraphExtension<AccountByPeriodEnq>
{
#region Actions
public PXAction<AccountByPeriodFilter> letter;
[PXUIField(DisplayName = "Letter")]
[PXButton]
protected virtual IEnumerable Letter(PXAdapter adapter)
{
return adapter.Get();
}
#endregion
}
}
With a base Acumatica install that's all that is needed for the Action to appear:
Note that you can specify explicit state and view rights for the button control, though I don't think your issue is related to access rights if you're working in a developer instance:
[PXUIField(DisplayName = "Letter",
MapEnableRights = PXCacheRights.Select,
MapViewRights = PXCacheRights.Select)]
I finally found my problem, I missed the public member for my class.
namespace PX.Objects.GL
{
public class AccountByPeriodEnqExtensions : PXGraphExtension<AccountByPeriodEnq>
{
#region Actions
public PXAction<AccountByPeriodFilter> Letter;
Thanks for your answer HB, i'll reuse it.
Related
I have a number of non persistent fields that get its value via FieldSelecting events. The events do the same queries and calculations so I decided to move them into an attribute that implements IPXFieldSelectingSubscriber interface. This works fine and it minimises duplicate codes. However, the event fires every single time the DAC is accessed. Be that in GIs or called in some other graphs. Is there such a flag to verify if the event is being fired only from a specific screen ?
The easiest way to do that would be to add the validation of which screen is accessing the DAC, you can do that by simply putting an if statement like below into your event handler.
if(graph.Accessinfo.ScreenID == "SO.30.10.00")
{
//execute the logic
}
else
{
//skip
}
An alternative solution would be to have the base field type declared on your DAC and then within a graph extension specific to the page you want the logic on, you would declare a CacheAttached event with the custom attribute.
DAC field :
public sealed class APInvoiceExtension : PXCacheExtension<APInvoice>
{
#region UsrTotal
public abstract class usrTotal : BqlDecimal.Field<usrTotal>
{
}
[PXDBDecimal]
[PXDefault(TypeCode.Decimal, "0.0", PersistingCheck = PXPersistingCheck.Nothing)]
[PXUIField(DisplayName = "Total")]
public decimal? UsrTotal { get; set; }
#endregion
}
Graph Extension :
public class APInvoiceEntryExtension : PXGraphExtension<APInvoiceEntry>
{
[PXMergeAttributes(Method = MergeMethod.Append)]
[CustomCalculationAttribute]
protected virtual void __(Events.CacheAttached<APInvoiceExtension.usrTotal> e)
{
}
}
We have a graph extension that manages the tab we added to a screen. The tab displays data from our own DAC. We do not use a DAC extension, not going to go into the reasons right now.
When a user opens the main screen I want to create a record of our data if it does not exist, with some defaulting business logic.
I added a RowSelected event handler on the main DAC and it fires as I expect it to. When I add the code to create our missing record in the event handler Acuminator gives me error "PX1044 Changes to PXCache cannot be performed in event handlers."
I understand the error that Acuminator is raising, but I'm not sure where else to create our record. I cannot remember a section of the university specifically addressing this scenario.
Can anyone tell me how I would handle this scenario? And if possible, point me at the learning material that covers this scenario for broader information.
Unfortunatly there are only a few OK ways to do this and make Acuminator happy. You will risk data inconsistency.
https://github.com/Acumatica/Acuminator/blob/master/docs/diagnostics/PX1044.md
I would reccomend putting your data insertion code in Row Updated and hope the user updates something
I got a reply from Acumatica support, you can do this in a view select delegate.
Here's some example code, I haven't tested it though:
public class InventoryItemMaintExtension : PXGraphExtension<InventoryItemMaint>
{
public SelectFrom<MyCustomTable>.
Where<MyCustomTable.inventoryID.IsEqual<
InventoryItem.inventoryID.FromCurrent>>.
View AdditionalItemData;
public static bool IsActive() { return true; }
public IEnumerable additionalItemData()
{
var data = AdditionalItemData.SelectSingle();
if (data is null)
{
data = new MyCustomTable();
AdditionalItemData.Insert(data);
}
return AdditionalItemData.Select();
}
}
[PXCacheName("Additional Item Data")]
[Serializable]
public class MyCustomTable : IBqlTable
{
#region CashAccountID
[Inventory]
[PXDefault]
public virtual int? InventoryID { get; set; }
public abstract class inventoryID : PX.Data.BQL.BqlInt.Field<inventoryID> { }
#endregion
#region Required
[PXDBBool()]
[PXDefault(false)]
[PXUIField(DisplayName = "Required")]
public virtual bool? Required { get; set; }
public abstract class required : PX.Data.BQL.BqlBool.Field<required> { }
#endregion
}
Does anyone have a code snippet on how to go from RefNoteId => DAC when I dont know what dac type the note is attached to?
I have made it this far (row.RefNoteID is what I am starting from)
Note note = PXSelect<Note, Where<Note.noteID, Equal<Required<Note.noteID>>>>.Select(this, row.RefNoteID);
Type recordType = Type.GetType(note.EntityType);
PXCache recordCache = Caches[recordType];
How can I now do a PXSelect<recordType, Where<recodType.noteID, Equal<Required<recordType.noteID>>>>.Select(GRAPH) ? The recordType could be any DAC in the system that has a noteID.
Thanks
The below code works for me and it is based on the way Acumatica gets the record inside the PXRefNoteSelectorAttribute.PrimaryRow_RowPersisted.
The problem with this approach is that this will work for Header entities like SOOrder, INRegister, SOInvoice, SOShipment, and others. But for "detail" entities like SOLine, INTran, and others this approach will work only if that corresponding record has some Note related to Text/File. Acumatica is adding records corresponding to their NoteID into the Note table only if that detail records have some Note/Text. My best guess is that this is done in order to avoid over-spamming the Note table.
using PX.Data;
using PX.Objects.SO;
using System;
using System.Collections;
using System.Linq;
using System.Web.Compilation;
namespace SearchByNoteID
{
// Acuminator disable once PX1016 ExtensionDoesNotDeclareIsActiveMethod extension should be constantly active
public class SOOrderEntryExt : PXGraphExtension<SOOrderEntry>
{
public PXAction<SOOrder> searchByNoteID;
[PXUIField(DisplayName ="Search by Note ID")]
[PXButton(CommitChanges = true)]
public virtual IEnumerable SearchByNoteID(PXAdapter adapter)
{
var order = adapter.Get<SOOrder>().FirstOrDefault();
if(order!=null)
{
//
//...
//
Guid? noteID = GetNoteID();
object record = GetRecordByNoteID(noteID);
//
//... do whatever you want with the record
//
}
return adapter.Get();
}
protected object GetRecordByNoteID(Guid? noteID)
{
var type = GetEntityType(this.Base, noteID);
if(type==null) return null;
object entityRow = new EntityHelper(this.Base).GetEntityRow(type, noteID);
return entityRow;
}
protected Type GetEntityType(PXGraph graph, Guid? noteID)
{
if (noteID == null)
{
return null;
}
Note note = PXSelectBase<Note, PXSelect<Note, Where<Note.noteID, Equal<Required<Note.noteID>>>>.Config>.SelectWindowed(graph, 0, 1, new object[]
{
noteID
});
if (note == null || string.IsNullOrEmpty(note.EntityType))
{
return null;
}
return PXBuildManager.GetType(note.EntityType, false);
}
}
}
I am trying to copy the Currency Behavior of APInvoiceEntry BLC.
I'm confused, I copied already all the events, but with no luck,
CurrencyInfo_RowUpdated does not fire event. I am certain that this event is the Currency Conversion when you Click View Cury or View Base.
Somehow I managed to save CuryInfoID information on the Database. I'm only getting confused on the client events.
Am I missing something? I copied also the Currency Rate and set it to my own Currency Rate view. so it is working. Please enlighten me.
Thanks!
The View Base/View Cury action is handled by the ToggleCurrency.Handler() method. The APInvoiceEntry graph has the ToggleCurrency<APInvoice> CurrencyView; member which drives the button. Here is an example on how you could override it in an APInvoiceEntry graph extension:
using PX.Data;
using PX.Objects.CM;
namespace PX.Objects.AP
{
public class APInvoiceEntry_Extension : PXGraphExtension<APInvoiceEntry>
{
public CustomToggleCurrency<APInvoice> CurrencyView;
}
public class CustomToggleCurrency<TNode> : ToggleCurrency<TNode>
where TNode : class, IBqlTable, new()
{
public CustomToggleCurrency(PXGraph graph, string name)
: base(graph, name)
{
}
[PXUIField(DisplayName = "Toggle Currency", MapEnableRights = PXCacheRights.Select, MapViewRights = PXCacheRights.Select)]
[PXButton(ImageKey = PX.Web.UI.Sprite.Main.Money, Tooltip = PX.Objects.CM.Messages.ToggleCurrencyViewTooltip)]
protected override System.Collections.IEnumerable Handler(PXAdapter adapter)
{
return base.Handler(adapter);
}
}
}
I have a custom Selector created using PXCustomSelectorAttribute class, I am not able to do AutoRefresh as this option is not available. Can anyone tell me how to Autorefresh the custom selector.
Below is an example how to create a CustomSelector and set it to AutoRefresh mode.
using PX.Objects.SO;
using PX.Objects.AR;
using PX.Data;
using System.Collections;
namespace TestLib
{
public class SOOrderExt : PXCacheExtension<SOOrder>
{
#region UsrTestField
[PXDBString]
[PXUIField(DisplayName = "TestField")]
[CustomerPriceClass()]
public virtual string UsrTestField { get; set; }
public abstract class usrTestField:IBqlField { }
#endregion
}
public class CustomerPriceClassAttribute : PXCustomSelectorAttribute
{
public CustomerPriceClassAttribute()
: base(typeof(ARPriceClass.priceClassID))
{
this.DescriptionField = typeof(ARPriceClass.description);
}
protected virtual IEnumerable GetRecords()
{
ARPriceClass epc = new ARPriceClass();
epc.PriceClassID = ARPriceClass.EmptyPriceClass;
epc.Description = PX.Objects.AR.Messages.BasePriceClassDescription;
yield return epc;
foreach (ARPriceClass pc in PXSelect<ARPriceClass>.
Select(this._Graph))
{
yield return pc;
}
}
}
}
After this you need to add the field to the UI from Layout Editor and set the AutoRefresh property in the Ext Properties section to True. See the screen-shot below.
UPDATE:
In case of Grid you need to add you will need to add Control in the Levels of the Grid like is shown on the screenshot below:
After adding the control you will see the Field Editor (3) for that field.
In the properties of the Field Editor the AutoRefresh is available and you can set it to True: