A formula in Acumatica is a DAC field that is calculated based on the values of other object fields.
To calculate a formula, Aсumatiсa framework provides a set of various operations and functions (such as arithmetical, logical, and comparison operations and string processing functions; see List of Built-In Common Formulas). In addition to the field values, a formula can use various constants provided by both the core of Acumatica and the application solutions. Moreover, a formula can obtain values for the calculation not only from the current record but also from other sources (see Formula Context and Its Modifiers).
The beauty of the formulas is that they will automatically recalculate the value at the right time:
FieldDefaulting
event handler of formula field)FieldUpdated
event handler of each dependent field)RowSelecting
event handler)RowPersisted
event handler)Recalculation of a formula field value on the update of a dependent field raises a FieldUpdated
event for formula field. This allows you to make a chain of dependent formulas (see Direct and Mediated Circular References in Formulas).
Application developers can write their own application-side formulas.
A formula can be used in three main modes:
There is another auxiliary mode, unbound formulas, that is very similar to mixed mode, but the calculated values of the formula are not assigned to the formula field. The aggregated value is calculated immediately and assigned to the field of the parent object. See Usage of Unbound Formulas for more information.
PXFormulaAttribute
Properties and Constructor ParametersThe formula functionality is implemented by PXFormulaAttribute
. The constructor of PXFormulaAttribute has the following signatures:
public PXFormulaAttribute(Type formulaType) { // ... }
The single parameter formulaType
is a type of formula expression to calculate the field value from other fields of the same data record. This parameter must meet one of the following conditions:
public PXFormulaAttribute(Type formulaType, Type aggregateType) { // ... }
The first parameter, formulaType
, is the same as in the first constructor. The second parameter, aggregateType
, is a type of aggregation formula to calculate the parent data record field from the child data record fields. An aggregation function can be used, such as SumCalc, CountCalc, MinCalc, and MaxCalc. Application developers can create their own aggregation formulas.
An aggregate formula type must be a generic type and must implement IBqlAggregateCalculator
interface. The first generic parameter of the aggregate formula type must implement the IBqlField
interface and must have the field type of the parent object.
public virtual bool Persistent { get; set; }
The PXFormulaAttribute.Persistent
property indicates whether the attribute recalculates the formula after changes are saved to the database. You may need recalculation if the fields the formula depends on are updated on the RowPersisting
event. By default, the property equals false
.
In most cases, formulas are used for direct calculation of the value of the formula field from other fields of the same data record.
The simplest example of formula usage:
[PXDBDate] [PXFormula(typeof(FADetails.receiptDate))] [PXDefault] [PXUIField(DisplayName = Messages.PlacedInServiceDate)] public virtual DateTime? DepreciateFromDate { get; set; }
In this example, the value of the ReceiptDate field is assigned to the DepreciateFromDate field on the insertion of a new record and on the update of the ReceiptDate field.
A slightly more complex example:
[PXCurrency(typeof(APPayment.curyInfoID), typeof(APPayment.unappliedBal))] [PXUIField(DisplayName = "Unapplied Balance", Visibility = PXUIVisibility.Visible, Enabled = false)] [PXFormula(typeof(Sub<APPayment.curyDocBal, APPayment.curyApplAmt>))] public virtual Decimal? CuryUnappliedBal { get; set; }
Here, the unapplied balance of the document is calculated as the difference between the balance of the document and the applied amount.
Example of multiple choice with a default value:
[PXUIField(DisplayName = "Class Icon", IsReadOnly = true)] [PXImage] [PXFormula(typeof(Switch< Case<Where<EPActivity.classID, Equal<CRActivityClass.task>>, EPActivity.classIcon.task, Case<Where<EPActivity.classID, Equal<CRActivityClass.events>>, EPActivity.classIcon.events, Case<Where<EPActivity.classID, Equal<CRActivityClass.email>, And<EPActivity.isIncome, NotEqual<True>>>, EPActivity.classIcon.email, Case<Where<EPActivity.classID, Equal<CRActivityClass.email>, And<EPActivity.isIncome, Equal<True>>>, EPActivity.classIcon.emailResponse, Case<Where<EPActivity.classID, Equal<CRActivityClass.history>>, EPActivity.classIcon.history>>>>>, Selector<Current2<EPActivity.type>, EPActivityType.imageUrl>>))] public virtual string ClassIcon { get; set; }
The order of fields in the DAC is important to correct formula calculation. All source fields (from which the formula is calculated) including other formulas must be defined in the DAC before the formula field. Otherwise, the field can be calculated incorrectly or can cause a runtime error.
By default, the context of the formula calculation is restricted by the current object (record) of the class containing the formula declaration. It is also allowed to use constants (descendants of the Constant<>
class).
A formula that uses the fields of its object only:
public partial class Contract : IBqlTable, IAttributeSupport { //... [PXDecimal(4)] [PXDefault(TypeCode.Decimal, "0.0", PersistingCheck = PXPersistingCheck.Nothing)] [PXFormula(typeof(Add<Contract.pendingRecurring, Add<Contract.pendingRenewal, Contract.pendingSetup>>))] [PXUIField(DisplayName = "Total Pending", Enabled=false)] public virtual decimal? TotalPending { get; set; } //... }
However, it is possible to obtain input values for the formula calculation from other sources:
PXSelectorAttribute
.PXParentAttribute
.The formula supports the following context modifiers.
Current<TRecord.field>
and Current2<TRecord.field>
Fetches the field value from the record stored in the Current
property of the TRecord cache.
If the cache's Current
property or the field itself contains null:
Example:
[PXFormula(typeof(Switch< Case<Where< ARAdjust.adjgDocType, Equal<Current<ARPayment.docType>>, And<ARAdjust.adjgRefNbr, Equal<Current<ARPayment.refNbr>>>>, ARAdjust.classIcon.outgoing>, ARAdjust.classIcon.incoming>))] protected virtual void ARAdjust_ClassIcon_CacheAttached(PXCache sender)
Parent<TParent.field>
Fetches the field value from the parent data record as defined by PXParentAttribute residing on the current DAC.
public class INTran : IBqlTable { [PXParent(typeof(Select< INRegister, Where< INRegister.docType, Equal<Current<INTran.docType>>, And<INRegister.refNbr,Equal<Current<INTran.refNbr>>>>>))] public virtual String RefNbr { ... } [PXFormula(typeof(Parent<INRegister.origModule>))] public virtual String OrigModule { ... } }
IsTableEmpty<TRecord>
Returns true
if the DB table corresponding to the specified DAC contains no records, false
otherwise.
public class APRegister : IBqlTable { [PXFormula(typeof(Switch< Case<Where< IsTableEmpty<APSetupApproval>, Equal<True>>, True, Case<Where< APRegister.requestApproval, Equal<True>>, False>>, True>))] public virtual bool? DontApprove { get; set; } }
Selector<KeyField, ForeignOperand>
public class APVendorPrice : IBqlTable { // Note: inventory attribute is an // aggregate containing a PXSelectorAttribute // inside, which is also valid for Selector<>. // - [Inventory(DisplayName = "Inventory ID")] public virtual int? InventoryID [PXFormula(typeof(Selector< APVendorPrice.inventoryID, InventoryItem.purchaseUnit>))] public virtual string UOM { get; set; } }
If the formula field is an unbound field marked with one of the PXFieldAttribute
descendants (such as PXIntAttribute
or PXStringAttribute
), then its calculation is additionally triggered during RowSelecting
event.
TBD
TBD
TBD
TBD