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 eventi RowSelecting )
  • 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:

  1. Condizione aggiuntiva del restrittore. Questo tipo di BQL deve implementare l'interfaccia IBqlWhere .
  2. 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 ).
  3. 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; }


Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow