acumatica
Riferimento agli attributi della piattaforma Acumatica
Ricerca…
Attributo PXFormula
Descrizione generale
Una formula in Acumatica è un campo DAC che viene calcolato in base ai valori di altri campi oggetto.
Per calcolare una formula, il framework Aumidi fornisce un insieme di varie operazioni e funzioni (come operazioni aritmetiche, logiche e di confronto e funzioni di elaborazione delle stringhe, vedere Elenco delle formule comuni incorporate ). Oltre ai valori del campo, una formula può utilizzare varie costanti fornite sia dal core di Acumatica sia dalle soluzioni applicative. Inoltre, una formula può ottenere valori per il calcolo non solo dal record corrente ma anche da altre fonti (vedi Contesto della formula e relativi modificatori ).
La bellezza delle formule è che ricalcoleranno automaticamente il valore al momento giusto:
- Su default del campo (inserimento di una nuova riga, gestore di eventi
FieldDefaulting
del campo formula) - All'aggiornamento dei campi dipendenti (gestore eventi
FieldUpdated
di ciascun campo dipendente) - Sulla selezione del database (solo per i campi non
RowSelecting
; gestore di eventiRowSelecting
) - Se il database persiste se necessario (lo sviluppatore deve specificarlo esplicitamente; gestore di eventi
RowPersisted
)
Il ricalcolo di un valore del campo formula sull'aggiornamento di un campo dipendente genera un evento FieldUpdated
per il campo formula. Ciò consente di creare una catena di formule dipendenti (vedere Riferimenti circolari diretti e mediati nelle formule).
Gli sviluppatori di applicazioni possono scrivere le proprie formule sul lato dell'applicazione.
Modalità di utilizzo
Una formula può essere utilizzata in tre modalità principali:
- Semplicemente calcolando il valore e assegnandolo al campo formula (vedi Uso di base )
- Calcolo del valore aggregato dai valori esistenti dei campi formula e assegnandolo al campo specificato nell'oggetto padre (vedere Uso aggregato )
- Modalità mista: calcolo del valore della formula, assegnazione al campo formula, calcolo del valore aggregato e assegnazione al campo nell'oggetto padre (vedere Uso combinato )
Esiste un'altra modalità ausiliaria, formule non associate, molto simile alla modalità mista, ma i valori calcolati della formula non sono assegnati al campo formula. Il valore aggregato viene calcolato immediatamente e assegnato al campo dell'oggetto genitore. Vedi Utilizzo di formule non legate per ulteriori informazioni.
Proprietà PXFormulaAttribute
e parametri del costruttore
La funzionalità della formula è implementata da PXFormulaAttribute
. Il costruttore di PXFormulaAttribute ha le seguenti firme:
public PXFormulaAttribute(Type formulaType) { // ... }
Il parametro singolo formulaType
è un tipo di espressione di formula per calcolare il valore del campo da altri campi dello stesso record di dati. Questo parametro deve soddisfare una delle seguenti condizioni:
- Deve implementare l'interfaccia IBqlField
- Deve essere una costante BQL
- Deve implementare l'interfaccia IBqlCreator (vedere Elenco di formule comuni incorporate )
public PXFormulaAttribute(Type formulaType, Type aggregateType) { // ... }
Il primo parametro, formulaType
, è lo stesso del primo costruttore. Il secondo parametro, aggregateType
, è un tipo di formula di aggregazione per calcolare il campo del record di dati parent dai campi del record di dati figlio. È possibile utilizzare una funzione di aggregazione, ad esempio SumCalc, CountCalc, MinCalc e MaxCalc. Gli sviluppatori di applicazioni possono creare le proprie formule di aggregazione.
Un tipo di formula aggregata deve essere di tipo generico e deve implementare IBqlAggregateCalculator
interfaccia IBqlAggregateCalculator
. Il primo parametro generico del tipo di formula aggregata deve implementare l'interfaccia IBqlField
e deve avere il tipo di campo dell'oggetto genitore.
public virtual bool Persistent { get; set; }
La proprietà PXFormulaAttribute.Persistent
indica se l'attributo ricalcola la formula dopo che le modifiche sono state salvate nel database. Potrebbe essere necessario ricalcolare se i campi da cui dipende la formula vengono aggiornati sull'evento RowPersisting
. Per impostazione predefinita, la proprietà è uguale a false
.
uso
Nella maggior parte dei casi, le formule vengono utilizzate per il calcolo diretto del valore del campo formula da altri campi dello stesso record di dati.
L'esempio più semplice di utilizzo della formula:
[PXDBDate] [PXFormula(typeof(FADetails.receiptDate))] [PXDefault] [PXUIField(DisplayName = Messages.PlacedInServiceDate)] public virtual DateTime? DepreciateFromDate { get; set; }
In questo esempio, il valore del campo ReceiptDate viene assegnato al campo DepreciateFromDate sull'inserimento di un nuovo record e sull'aggiornamento del campo ReceiptDate.
Un esempio leggermente più complesso:
[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; }
Qui, il saldo non applicato del documento viene calcolato come differenza tra il saldo del documento e l'importo applicato.
Esempio di scelta multipla con un valore predefinito:
[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; }
Ordine dei campi
L'ordine dei campi nel DAC è importante per correggere il calcolo della formula. Tutti i campi di origine (da cui viene calcolata la formula), comprese altre formule, devono essere definiti nel DAC prima del campo formula. In caso contrario, il campo può essere calcolato in modo errato o può causare un errore di runtime.
Contesto della formula e suoi modificatori
Per impostazione predefinita, il contesto del calcolo della formula è limitato dall'oggetto corrente (record) della classe che contiene la dichiarazione della formula. È anche permesso l'uso di costanti (discendenti della classe Constant<>
).
Una formula che utilizza solo i campi del suo oggetto:
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; } //... }
Tuttavia, è possibile ottenere valori di input per il calcolo della formula da altre fonti:
- Un record corrente di qualsiasi cache nel BLC (se assegnato).
- Un record straniero specificato da
PXSelectorAttribute
. - Un record padre specificato da
PXParentAttribute
.
La formula supporta i seguenti modificatori di contesto.
Current<TRecord.field>
e Current2<TRecord.field>
Rileva il valore del campo dal record archiviato nella proprietà Current
della cache TRecord.
Se la proprietà Current
della cache o il campo stesso contiene null:
- Attuale <> forza il campo predefinito e restituisce il valore del campo predefinito.
- Current2 <> restituisce null.
Esempio:
[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>
Rileva il valore del campo dal record di dati padre come definito da PXParentAttribute che risiede sul DAC corrente.
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>
Restituisce true
se la tabella DB corrispondente al DAC specificato non contiene record, false
contrario.
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>
Rileva un PXSelectorAttribute definito nel campo chiave esterna (KeyField) del DAC corrente.
Rileva il record di dati estranei a cui fa riferimento il selettore.
Calcola e restituisce un'espressione su quel record di dati come definito da 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; } }
Utilizzo di formule nei campi non associati
Se il campo formula è un campo non PXFieldAttribute
contrassegnato da uno dei discendenti PXFieldAttribute
(come PXIntAttribute
o PXStringAttribute
), il suo calcolo viene inoltre attivato durante RowSelecting
evento RowSelecting
.
Elenco di formule comuni incorporate
TBD
Riferimenti circolari diretti e mediati nelle formule
TBD
Flusso di controllo in formule condizionali
TBD
Utilizzo di più formule su un campo
TBD
Attributo PXRestrictor
introduzione
L'attributo PXSelectorAttribute (noto anche come selettore), sebbene vitale e frequentemente utilizzato, presenta tuttavia due principali svantaggi:
- Fornisce un messaggio non informativo
"<object_name> cannot be found in the system"
se non vengono trovati elementi per soddisfare la condizione del selettore. - Viene generato lo stesso messaggio di errore se si aggiornano altri campi del record ma l'oggetto a cui fa riferimento il selettore è già stato modificato e non soddisfa più la sua condizione. Questo comportamento è chiaramente sbagliato perché la legge non deve essere retroattiva.
Il PXRestrictorAttribute
( PXRestrictorAttribute
anche come limitatore) può essere utilizzato per risolvere questi problemi.
Dettagli
PXRestrictorAttribute
non funziona da solo; dovrebbe sempre essere abbinato a un PXSelectorAttribute
. L'uso del limitatore senza selettore non avrà alcun effetto.
Il restrittore trova il selettore sullo stesso campo, inserendo in esso una condizione aggiuntiva e il corrispondente messaggio di errore. La condizione del limitatore viene aggiunta alla condizione del selettore tramite un valore booleano AND e viene generato un messaggio di errore appropriato se l'oggetto di riferimento viola il vincolo del limitatore. Inoltre, se l'oggetto di riferimento è stato modificato e non soddisfa più la condizione di restrizione, non viene prodotto alcun messaggio di errore quando si modificano altri campi dell'oggetto di riferimento.
Uso generale:
[PXDBInt] [PXSelector(typeof(Search<FAClass.assetID, Where<FAClass.recordType, Equal<FARecordType.classType>>>), typeof(FAClass.assetCD), typeof(FAClass.assetTypeID), typeof(FAClass.description), typeof(FAClass.usefulLife), SubstituteKey = typeof(FAClass.assetCD), DescriptionField = typeof(FAClass.description), CacheGlobal = true)] [PXRestrictor(typeof(Where<FAClass.active, Equal<True>>), Messages.InactiveFAClass, typeof(FAClass.assetCD))] [PXUIField(DisplayName = "Asset Class", Visibility = PXUIVisibility.Visible)] public virtual int? ClassID { get; set; }
È possibile utilizzare più restrittori con un attributo selettore. In questo caso, tutte le condizioni di restrizione aggiuntive vengono applicate in un ordine non determinato. Una volta violata una qualsiasi condizione, viene generato il messaggio di errore appropriato.
La condizione Where<>
del selettore stesso viene applicata dopo tutte le condizioni di restrizione.
[PXDefault] // An aggregate attribute containing the selector inside. // - [ContractTemplate(Required = true)] [PXRestrictor(typeof(Where<ContractTemplate.status, Equal<Contract.status.active>>), Messages.TemplateIsNotActivated, typeof(ContractTemplate.contractCD))] [PXRestrictor(typeof(Where<ContractTemplate.effectiveFrom, LessEqual<Current<AccessInfo.businessDate>>, Or<ContractTemplate.effectiveFrom, IsNull>>), Messages.TemplateIsNotStarted)] [PXRestrictor(typeof(Where<ContractTemplate.discontinueAfter, GreaterEqual<Current<AccessInfo.businessDate>>, Or<ContractTemplate.discontinueAfter, IsNull>>), Messages.TemplateIsExpired)] public virtual int? TemplateID { get; set; }
Opzioni
Il costruttore di PXRestrictorAttribute accetta tre parametri:
- Condizione aggiuntiva del restrittore. Questo tipo di BQL deve implementare l'interfaccia
IBqlWhere
. - Il messaggio di errore appropriato. Il messaggio può contenere elementi di formato (parentesi graffe) per mostrare il contesto. Il messaggio deve essere una costante di stringa definita in una classe statica localizzabile (come
PX.Objects.GL.Messages
). - Una serie di tipi di campo. Questi campi devono appartenere all'oggetto corrente e devono implementare l'interfaccia
IBqlField
. I valori dei campi verranno utilizzati per la formattazione dei messaggi di errore.
Inoltre, ci sono diverse opzioni che specificano il comportamento del limitatore.
Overriding Restrictor ereditati
La proprietà ReplaceInherited
indica se il ReplaceInherited
corrente deve sovrascrivere i restrittori ereditati. Se questa proprietà è impostata su true, tutti i restrittori ereditati (posizionati su qualsiasi attributo aggregato o attributo di base) verranno sostituiti.
Sostituzione dei restrittori ereditati:
[CustomerActive(Visibility = PXUIVisibility.SelectorVisible, Filterable = true, TabOrder = 2)] [PXRestrictor(typeof(Where<Customer.status, Equal<CR.BAccount.status.active>, Or<Customer.status, Equal<CR.BAccount.status.oneTime>, Or<Customer.status, Equal<CR.BAccount.status.hold>, Or<Customer.status, Equal<CR.BAccount.status.creditHold>>>>>), Messages.CustomerIsInStatus, typeof(Customer.status), ReplaceInherited = true)] // Replaced all restrictors from CustomerActiveAttribute [PXUIField(DisplayName = "Customer")] [PXDefault()] public override int? CustomerID { get; set; }
Si noti che non è consigliabile utilizzare la proprietà ReplaceInherited
nel codice dell'applicazione quando esistono alternative ragionevoli. Questa proprietà è pensata principalmente per essere utilizzata nelle personalizzazioni.
Caching globale
CacheGlobal
supporta la funzionalità di dizionario globale nello stesso modo in PXSelectorAttribute
.
Raccomandazioni per l'utilizzo
Utilizzare solo le condizioni del limitatore
Quando i vincoli e un selettore vengono utilizzati insieme, quest'ultimo non deve contenere la clausola IBqlWhere
. Idealmente, tutte le condizioni dovrebbero essere spostate in limitatori. Questo approccio fornisce messaggi di errore più user-friendly ed elimina errori retroattivi non necessari.
Un esempio ideale:
[PXDBString(5, IsFixed = true, IsUnicode = false)] [PXUIField(DisplayName = "Type", Required = true)] [PXSelector(typeof(EPActivityType.type), DescriptionField = typeof(EPActivityType.description))] [PXRestrictor(typeof(Where<EPActivityType.active, Equal<True>>), Messages.InactiveActivityType, typeof(EPActivityType.type))] [PXRestrictor(typeof(Where<EPActivityType.isInternal, Equal<True>>), Messages.ExternalActivityType, typeof(EPActivityType.type))] public virtual string Type { get; set; }
Possibili errori retroattivi:
[PXDBInt] [PXUIField(DisplayName = "Contract")] [PXSelector(typeof(Search2<Contract.contractID, LeftJoin<ContractBillingSchedule, On<Contract.contractID, Equal<ContractBillingSchedule.contractID>>>, Where<Contract.isTemplate, NotEqual<True>, And<Contract.baseType, Equal<Contract.ContractBaseType>, And<Where<Current<CRCase.customerID>, IsNull, Or2<Where<Contract.customerID, Equal<Current<CRCase.customerID>>, And<Current<CRCase.locationID>, IsNull>>, Or2<Where<ContractBillingSchedule.accountID, Equal<Current<CRCase.customerID>>, And<Current<CRCase.locationID>, IsNull>>, Or2<Where<Contract.customerID, Equal<Current<CRCase.customerID>>, And<Contract.locationID, Equal<Current<CRCase.locationID>>>>, Or<Where<ContractBillingSchedule.accountID, Equal<Current<CRCase.customerID>>, And<ContractBillingSchedule.locationID, Equal<Current<CRCase.locationID>>>>>>>>>>>>, OrderBy<Desc<Contract.contractCD>>>), DescriptionField = typeof(Contract.description), SubstituteKey = typeof(Contract.contractCD), Filterable = true)] [PXRestrictor(typeof(Where<Contract.status, Equal<Contract.status.active>>), Messages.ContractIsNotActive)] [PXRestrictor(typeof(Where<Current<AccessInfo.businessDate>, LessEqual<Contract.graceDate>, Or<Contract.expireDate, IsNull>>), Messages.ContractExpired)] [PXRestrictor(typeof(Where<Current<AccessInfo.businessDate>, GreaterEqual<Contract.startDate>>), Messages.ContractActivationDateInFuture, typeof(Contract.startDate))] [PXFormula(typeof(Default<CRCase.customerID>))] [PXDefault(PersistingCheck = PXPersistingCheck.Nothing)] public virtual int? ContractID { get; set; }