Get current cache outside of graph - acumatica

I've created a custom selector that has logic which depends on the value of a field in a header section of a screen. Since the logic is not in the graph which holds the views, how would I obtain the current value of the cache for this header section? I've set the field I'm referencing in the header to commitchanges=true and I've even put SyncPosition=true in the header section of the page. The following logic does not give me the current value that is (I'm assuming) in the cache:
mh = (xTACMappingHeader)PXSelect< xTACMappingHeader,
Where< xTACMappingHeader.mappingName, Equal<Required<xTACMappingDetail.mappingName>>>>.Select(new PXGraph<FinancialTranslatorMaint>(), md.MappingName);
What's the best way to retrieve the current value of the cache in a graph outside of that graph?
Thanks...

You get hold of graph using CacheAttached event. See example below.
public class YourAttribute : PXEventSubscriberAttribute
{
private PXGraph _Graph = null;
public override void CacheAttached(PXCache sender)
{
_Graph = sender.Graph;
base.CacheAttached(sender);
}
}

PXCache objects never exist outside of graph. You can access current graph through the _Graph field of PXCustomSelectorAttribute:
protected PXGraph _Graph;
something like:
mh = (xTACMappingHeader)PXSelect<…>.Select(_Graph, md.MappingName);
to access current value of the cache:
_Graph.Caches[typeof(YourDAC)].Current
While initializing caches, Acumatica Framework invokes CacheAttached() method on for every field attribute. PXCustomSelectorAttribute assigns value for the _Graph field based on Graph property of the currently initializing PXCache object:
public class PXCustomSelectorAttribute : PXSelectorAttribute
{
...
public override void CacheAttached(PXCache sender)
{
...
_Graph = sender.Graph;
...
}
...
}

Related

Serialize extracted (custom attribute) value in Hazelcast

I am using Extractor for querying based on custom attributes. Here is the sample code.
#Override
public void extract(Object arg0, Object arg1, ValueCollector valueCollector) {
//date = domainVO.getDomainDate();
//valueCollector.addObject(age);
ValueReader valueReader = (ValueReader)arg0;
valueReader.read("domain.collectionData[any].status", (ValueCallback<String>)(value) -> {
valueCollector.addObject(value);
});
}
Custom attribute configuration
AttributeConfig attributeConfig = new AttributeConfig().setName("status")
.setExtractorClassName("com.sample.test.extractor.DomainValueExtractor");
Here client receives the entire entry object while querying the data.
Is it possible to fetch the custom attribute 'status' from client ?

How can I filter records for the Generate Recurring Entries (GL504000) screen

I'm trying to modify the Generate Recurring Transactions (GL504000) screen, and I want to add a user field to filter the grid. I've added a UsrRecurringClass user field to DAC extensions of both the 'Parameters' DAC for the header, and the 'Schedule' DAC for the grid. Now I want to filter that grid by the selection I've added to the header (The UsrRecurringClass field I've added to both)
The problem is, I can't add that field to the View select (Schedule_List) and have it make any difference (I did set the CommitChanges to true on the header filter field).
I've added this to graph extension of the 'ScheduleRun' BLC, as follows, but it doesn't seem to make any difference...
[PXFilterable]
public PXFilteredProcessing<Schedule, ScheduleRun.Parameters,
Where2<Where<ParametersExt.usrRecurringClass, IsNull, Or<ScheduleExt.usrRecurringClass, Equal<Current<ParametersExt.usrRecurringClass>>>>,
And2<Where<Schedule.active, Equal<True>>,
And<Schedule.nextRunDate, LessEqual<Current<ScheduleRun.Parameters.executionDate>>>>>> Schedule_List;
Maybe I'm not doing the BQL correctly, or there's a better way, using the View delegate - I'm not sure.
Any ideas?
I have done a similar customization, maybe the pattern will help you.
public class ARCreateWriteOff_Extension : PXGraphExtension<ARCreateWriteOff>
{
#region Event Handlers
// First expose the PXFilterable view in the extension
[PXFilterable]
[PX.SM.PXViewDetailsButton(typeof(ARRegisterEx.refNbr), WindowMode = PXRedirectHelper.WindowMode.NewWindow)]
public PXFilteredProcessingJoin<ARRegisterEx> ARDocumentList;
// the ARDocumentList view is a long BQL statement I just removed most of it for the example
// Over write the IEnumerabel
protected virtual IEnumerable aRDocumentList()
{
// Get the current row and its extention
ARWriteOffFilter aRWriteOffFilter = Base.Filter.Current;
ARWriteOffFilterExt aRWriteOffFilterExt = aRWriteOffFilter.GetExtension<ARWriteOffFilterExt>();
// loop the values
foreach (ARRegisterEx item in Base.ARDocumentList.Select())
{
//check if the field is Null to return all data
if (string.IsNullOrWhiteSpace(aRWriteOffFilterExt.UsrEmployeeID))
{
yield return item;
}
else
{
// Here you will check if your filter matches the row level
if (aRWriteOffFilterExt.UsrEmployeeID == bAccountExt.UsrEmployeeID)
{
yield return item;
}
}
}
}

Global Generic Field Update Event

I have 50+ custom paired fields "Inches" and "Centimeters", each enabled and editable. I need to update "Inches" if the user changed the value of "Centimeters" and visa verse. I was able to do this using SetValuePending on one of the paired fields and SetValueExt on the other during the Field Updated Event. My question, is there a way to do this on a higher level without having to do a Field_Updated event for all the 100+ fields. I know that Formulas would create a circular reference so cannot be used. Thanks
Well, you can use one method to handle FieldUpdated events for all fields you need using graph FieldUpdated.AddHandler method in constructor. To get a field name just extend a standard Acumatica FieldUpdated delegate with one additional parameter (name for example) and put it during the FieldUpdated.AddHandler call.
Here is an example with "Invoices and Memos" screen and ARInvoiceEntry graph.
public ARInvoiceEntry()
{
FieldUpdated.AddHandler(typeof(ARTran), typeof(ARTran.inches).Name, (sender, e) => CommonFieldUpdated(sender, e, typeof(ARTran.inches).Name));
FieldUpdated.AddHandler(typeof(ARTran), typeof(ARTran.centimeters).Name, (sender, e) => CommonFieldUpdated(sender, e, typeof(ARTran.centimeters).Name));
...
}
protected virtual void CommonFieldUpdated(PXCache sender, PXFieldUpdatedEventArgs e, string name)
{
// do something
}
Moreover, you can add handlers dynamically using fields collection for example
foreach(var field in Caches[typeof(ARTran)].Fields)
{
// add handler depends on field name
}
You can do it by PXFormula + ExternalValue BQL expression (the same as PXRowUpdatedEventArgs.ExternalCall for example), which will prevent circular reference between pair fields. The idea is to calculate field only when a related field has been changed by the user from UI (ExternalCall = true) and skip calculation when related field updated by the formula (ExternalCall = false).
public class centimetersInInches : PX.Data.BQL.BqlDecimal.Constant<centimetersInInches>
{
public centimetersInInches() : base(2.54m) { }
}
[PXDecimal]
[PXUIField(DisplayName = "Inches")]
[PXUnboundDefault(TypeCode.Decimal, "0.0")]
[PXFormula(typeof(ExternalValue<Div<centimeters, centimetersInInches>>))]
public virtual decimal? Inches { get; set; }
public abstract class inches : PX.Data.BQL.BqlDecimal.Field<inches> { }
[PXDecimal]
[PXUIField(DisplayName = "Centimeters")]
[PXUnboundDefault(TypeCode.Decimal, "0.0")]
[PXFormula(typeof(ExternalValue<Mult<inches, centimetersInInches>>))]
public virtual decimal? Centimeters { get; set; }
public abstract class centimeters : PX.Data.BQL.BqlDecimal.Field<centimeters> { }
And aspx
<px:PXGridColumn DataField="Inches" CommitChanges="True" />
<px:PXGridColumn DataField="Centimeters" CommitChanges="True" />
You can use the RowUpdated event and compare the old row with the new row to detect which field changed. I agree that keeping all logic in a single method is preferable.
public virtual void DAC_RowUpdated(PXCache sender, PXRowUpdatedEventArgs e)
{
DAC row = e.Row as DAC;
DAC oldRow = e.OldRow as DAC;
if (row == null || oldRow == null) return;
// Compare old row with new row to determine which field changed
if (row.Inches != oldRow.Inches)
{
// Inches field changed, update CM value
row.CM = row.Inches * INCHES_TO_CM_CONSTANT;
}
// Add more conditions for the other fields
[..]
}
I know that Formulas would create a circular reference so cannot be
used.
Yes I wouldn't recommend it either, you could use the DAC property Setter though.
public string _Inches
public virtual string Inches
{
get
{
return this._Inches;
}
set
{
this._Inches = value;
this.CM = value * INCHES_TO_CM_CONSTANT;
}
}
For all solution (except Formula/DAC attributes) I think a condition to stop recursion should be possible if it's absolutely necessary:
if (this.CM != value * INCHES_TO_CM_CONSTANT)
this.CM = value * INCHES_TO_CM_CONSTANT;
Ideally proper use/avoidance of SetValueExt to control when events are raised (Ext method raises events) would be enough to stop infinite loops.

Add note to custom data record in code

I was searching for a solution to add a note to a database row I am creating in a custom table. I found the solution below from Ruslan for accessing the noteid, but I don't understand how this would be used to add a note to the row. I have all the code to create the row, I just need the attributes or function call to actually attach the text of the note to the row.
==================================================================
To have Note record automatically created when a new parent record gets saved, one should invoke the static PXNoteAttribute.GetNoteID(PXCache cache, object data) method when the parent record is inserted in the cache.
For example, to have Note record automatically created when a new Stock Item gets saved, you should subscribe to RowInserted handler for the InventoryItem DAC and call PXNoteAttribute.GetNoteID(...):
public class InventoryItemMaintExt : PXGraphExtension<InventoryItemMaint>
{
public void InventoryItem_RowInserted(PXCache sender, PXRowInsertedEventArgs e)
{
var noteCache = Base.Caches[typeof(Note)];
var oldDirty = noteCache.IsDirty;
PXNoteAttribute.GetNoteID<InventoryItem.noteID>(sender, e.Row);
noteCache.IsDirty = oldDirty;
}
}
The code snippet above can be incorporated into almost any custom BLC with a couple simple changes to replace InventoryItem with a custom DAC.
After implementing Gabriel's suggestions:
I do not seem to get any note in the database. The code compiles and runs fine, but the notes are not generated as far as I can tell. The note id is set in my table, but no data appears in the note table. Please take a look at my code and let me know what needs to change. Also, how do I get the note column into a grid, or does it automatically become available when it is done correctly?
Database field definition:
[NoteID] [uniqueidentifier] NULL
DAC field:
#region NoteID
public abstract class noteID : PX.Data.IBqlField
{
}
protected Guid? _NoteID;
[PXNote()]
public virtual Guid? NoteID
{
get
{
return this._NoteID;
}
set
{
this._NoteID = value;
}
}
#endregion
Code to create record:
private static bool acumaticaException(Exception e, EDImportExceptionMaint excpMaint, LingoRet850 res850)
{
excpMaint.Clear();
var except = new EDImportExcept();
except.ExceptReason = "U";
except.Active = true;
<...field assignments...>
except.OrderNbr = "";
PXNoteAttribute.SetNote(excpMaint.Exception.Cache, excpMaint.Exception.Current,
((PX.Data.PXOuterException)e).InnerMessages[0] + "-" + e.Message);
excpMaint.Exception.Insert(except);
excpMaint.Actions.PressSave();
return true;
}
To set the note of a record, use the SetNote()static function of PXNoteAttribute
PXNoteAttribute.SetNote(Base.Item.Cache, Base.Item.Current, "Hello, World!");
Calling SetNote will also take care of adding the Note record if it doesn't exist, so you don't have to call GetNoteID before setting the note value as in your question.
P.S. There is also a GetNote function which allows you to retrieve the current value of the note:
string note = PXNoteAttribute.GetNote(Base.Item.Cache, Base.Item.Current);

Create Orchard ContentItem programmatically from Command

I've written the following simple command inside my module. The Faq type has one custom part with a single field, and one BodyPart. After _cm.Create(item) is run, the item has an Id assigned but I can't find any trace of it in the database and it doesn't appear in Orchard's content tab. Why does the item get an Id but isn't found in the database? And does it need a driver, view, and placement info before it appears in the content tab?
public class ApiCommands : DefaultOrchardCommandHandler
{
private readonly IContentManager _cm;
public ApiCommands(IContentManager cm)
{
_cm = cm;
}
[CommandName("api seed")]
public void Seed()
{
var item = _cm.New("Faq");
item.As<FaqPart>().Question = "Why is the sky blue?";
item.As<BodyPart>().Text = "Shut up and do your homework.";
_cm.Create(item);
}
}
My custom part has no driver this is the Handler:
public FaqHandler(IRepository<FaqPartRecord> repository)
{
Filters.Add(StorageFilter.For(repository));
}
It turns out my type didn't attach a CommonPart. After I attached one and set the Owner property of the part, I was able to save it.

Resources