Recherche…


Attribut PXFormula

Description générale

Une formule dans Acumatica est un champ DAC calculé en fonction des valeurs des autres champs d'objet.

Pour calculer une formule, Aсumatiсa framework fournit un ensemble de diverses opérations et fonctions (telles que les opérations arithmétiques, logiques et de comparaison, ainsi que les fonctions de traitement de chaînes; voir Liste des formules communes intégrées ). En plus des valeurs de champ, une formule peut utiliser diverses constantes fournies par le noyau d'Acumatica et les solutions d'application. De plus, une formule peut obtenir des valeurs pour le calcul non seulement à partir de l'enregistrement en cours mais également à partir d'autres sources (voir Contexte de formule et ses modificateurs ).

La beauté des formules est qu'elles recalculeront automatiquement la valeur au bon moment:

  • Champ par défaut (insertion d'une nouvelle ligne; FieldDefaulting événements FieldDefaulting du champ de formule)
  • Lors de la mise à jour des champs dépendants ( FieldUpdated événement FieldUpdated de chaque champ dépendant)
  • Sélection de la base de données (uniquement pour les champs non liés; RowSelecting événements RowSelecting )
  • Sur la base de données persistant si nécessaire (le développeur doit le spécifier explicitement; RowPersisted événements RowPersisted )

Le recalcul d'une valeur de champ de formule lors de la mise à jour d'un champ dépendant déclenche un événement FieldUpdated pour le champ de formule. Cela vous permet de créer une chaîne de formules dépendantes (voir Références circulaires directes et médiées dans les formules).

Les développeurs d'applications peuvent écrire leurs propres formules côté application.

Modes d'utilisation

Une formule peut être utilisée dans trois modes principaux:

  • Calculer simplement la valeur et l'assigner au champ de formule (voir Utilisation de base )
  • Calcul de la valeur d'agrégation à partir des valeurs existantes des champs de formule et affectation à la zone spécifiée dans l'objet parent (voir Utilisation de l'agrégat )
  • Mode mixte: calculer la valeur de la formule, l'affecter au champ de formule, calculer la valeur d'agrégation et l'affecter au champ de l'objet parent (voir Utilisation combinée )

Il existe un autre mode auxiliaire, les formules non liées, qui est très similaire au mode mixte, mais les valeurs calculées de la formule ne sont pas affectées au champ de formule. La valeur agrégée est calculée immédiatement et affectée au champ de l'objet parent. Voir Utilisation des formules non liées pour plus d'informations.

Propriétés PXFormulaAttribute et paramètres du constructeur

La fonctionnalité de la formule est implémentée par PXFormulaAttribute . Le constructeur de PXFormulaAttribute a les signatures suivantes:

public PXFormulaAttribute(Type formulaType)
{
    // ...
}

Le paramètre unique formulaType est un type d'expression de formule permettant de calculer la valeur du champ à partir d'autres champs du même enregistrement de données. Ce paramètre doit remplir l'une des conditions suivantes:

  • Doit implémenter l'interface IBqlField
  • Doit être une constante BQL
  • Doit implémenter l'interface IBqlCreator (voir Liste des formules communes intégrées )
public PXFormulaAttribute(Type formulaType, Type aggregateType)
{
    // ...
}

Le premier paramètre, formulaType , est le même que dans le premier constructeur. Le second paramètre, aggregateType , est un type de formule d'agrégation permettant de calculer le champ d'enregistrement de données parent à partir des champs d'enregistrement de données enfants. Une fonction d'agrégation peut être utilisée, telle que SumCalc, CountCalc, MinCalc et MaxCalc. Les développeurs d'applications peuvent créer leurs propres formules d'agrégation.

Un type de formule d'agrégation doit être un type générique et doit implémenter l'interface IBqlAggregateCalculator . Le premier paramètre générique du type de formule d'agrégation doit implémenter l'interface IBqlField et doit avoir le type de champ de l'objet parent.

public virtual bool Persistent { get; set; }

La propriété PXFormulaAttribute.Persistent indique si l'attribut recalcule la formule une fois les modifications enregistrées dans la base de données. Vous devrez peut-être effectuer un nouveau calcul si les champs dont dépend la formule sont mis à jour lors de l'événement RowPersisting . Par défaut, la propriété est égale à false .

Usage

Dans la plupart des cas, les formules sont utilisées pour le calcul direct de la valeur du champ de formule à partir d'autres champs du même enregistrement de données.

L'exemple le plus simple d'utilisation d'une formule:

[PXDBDate]
[PXFormula(typeof(FADetails.receiptDate))]
[PXDefault]
[PXUIField(DisplayName = Messages.PlacedInServiceDate)]
public virtual DateTime? DepreciateFromDate { get; set; }

Dans cet exemple, la valeur du champ ReceiptDate est affectée au champ DepreciateFromDate lors de l'insertion d'un nouvel enregistrement et lors de la mise à jour du champ ReceiptDate.

Un exemple légèrement plus complexe:

[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; }

Ici, le solde non appliqué du document est calculé comme la différence entre le solde du document et le montant appliqué.

Exemple de choix multiple avec une valeur par défaut:

[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; }

Ordre des champs

L'ordre des champs dans le DAC est important pour corriger le calcul des formules. Tous les champs source (à partir desquels la formule est calculée), y compris les autres formules, doivent être définis dans le DAC avant le champ de formule. Sinon, le champ peut être calculé de manière incorrecte ou peut provoquer une erreur d'exécution.

Contexte de formule et ses modificateurs

Par défaut, le contexte du calcul de la formule est restreint par l'objet actuel (enregistrement) de la classe contenant la déclaration de formule. Il est également permis d'utiliser des constantes (descendants de la classe Constant<> ).

Une formule qui utilise uniquement les champs de son objet:

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; }
    //...
}

Cependant, il est possible d'obtenir des valeurs d'entrée pour le calcul de la formule à partir d'autres sources:

  • Un enregistrement en cours de n'importe quel cache dans le BLC (si assigné).
  • Un enregistrement étranger spécifié par PXSelectorAttribute .
  • Un enregistrement parent spécifié par PXParentAttribute .

La formule prend en charge les modificateurs de contexte suivants.

Current<TRecord.field> et Current2<TRecord.field>

Récupère la valeur du champ de l'enregistrement stocké dans la propriété Current du cache TRecord.

Si la propriété Current ou le champ du cache contient null:

  • Le champ <> force le champ par défaut et renvoie la valeur du champ par défaut.
  • Current2 <> renvoie null.

Exemple:

[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>

Récupère la valeur de champ de l'enregistrement de données parent tel que défini par PXParentAttribute résidant sur le DAC en cours.

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>

Renvoie true si la table de base de données correspondant au DAC spécifié ne contient aucun enregistrement, sinon false .

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>

Récupère un PXSelectorAttribute défini sur le champ de clé étrangère (KeyField) du DAC actuel.

Récupère l'enregistrement de données étrangères actuellement référencé par le sélecteur.

Calcule et renvoie une expression sur cet enregistrement tel que défini par 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; }
}

Utilisation de formules sur des champs non liés

Si le champ de formule est un champ PXFieldAttribute de la PXFieldAttribute avec l'un des descendants de PXFieldAttribute (tel que PXIntAttribute ou PXStringAttribute ), son calcul est en outre déclenché lors de l'événement RowSelecting .

Liste des formules communes intégrées

À déterminer

Références circulaires directes et médiées dans les formules

À déterminer

Contrôle du flux dans les formules conditionnelles

À déterminer

Utiliser plusieurs formules sur un seul champ

À déterminer

Attribut PXRestrictor

introduction

L'attribut PXSelectorAttribute (également appelé sélecteur), bien que vital et fréquemment utilisé, présente cependant deux inconvénients majeurs:

  • Il donne un message non informatif "<object_name> cannot be found in the system" si aucun élément ne satisfait à la condition du sélecteur.
  • Le produit le même message d'erreur si vous mettez à jour d' autres champs de l'enregistrement, mais l'objet référencé par le sélecteur a déjà changé et ne répond plus à sa condition. Ce comportement est clairement erroné car la loi ne doit pas être rétroactive.

Le PXRestrictorAttribute (également appelé restricteur) peut être utilisé pour résoudre ces problèmes.

Détails

PXRestrictorAttribute ne fonctionne pas seul; il doit toujours être associé à un PXSelectorAttribute . L'utilisation du restricteur sans le sélecteur n'aura aucun effet.

Le restricteur trouve le sélecteur sur le même champ, y injectant une condition supplémentaire et le message d'erreur correspondant. La condition de restriction est ajoutée à la condition de sélecteur via un booléen AND, et un message d'erreur approprié est généré si l'objet référencé viole la contrainte de restriction. De plus, si l'objet référencé a changé et ne répond plus à la condition de restriction, aucun message d'erreur n'est généré lorsque vous modifiez d'autres champs de l'objet référençant.

Usage général:

[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; }

Plusieurs restricteurs peuvent être utilisés avec un seul attribut de sélecteur. Dans ce cas, toutes les conditions de restriction supplémentaires sont appliquées dans un ordre non déterminé. Une fois qu'une condition est violée, le message d'erreur approprié est généré.

La condition Where<> du sélecteur lui-même est appliquée après toutes les conditions de restriction.

[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; }

Les options

Le constructeur de PXRestrictorAttribute prend trois paramètres:

  1. La condition supplémentaire du restricteur. Ce type BQL doit implémenter l'interface IBqlWhere .
  2. Le message d'erreur approprié. Le message peut contenir des éléments de format (accolades) pour afficher le contexte. Le message doit être une constante de chaîne définie dans une classe statique localisable (telle que PX.Objects.GL.Messages ).
  3. Un tableau de types de champs. Ces champs doivent appartenir à l'objet actuel et implémenter l'interface IBqlField . Les valeurs des champs seront utilisées pour le formatage des messages d'erreur.

En outre, plusieurs options spécifient le comportement du limiteur.

Remplacement des restrictions héritées

La propriété ReplaceInherited indique si le restricteur actuel doit remplacer les restricteurs hérités. Si cette propriété est définie sur true, tous les restricteurs hérités (placés sur des attributs d'agrégat ou des attributs de base) seront remplacés.

Remplacement des limiteurs hérités:

 [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; }

Notez que nous vous déconseillons d'utiliser la propriété ReplaceInherited dans le code de l'application lorsque des alternatives raisonnables existent. Cette propriété est principalement destinée à être utilisée dans les personnalisations.

Mise en cache globale

CacheGlobal prend en charge la fonctionnalité de dictionnaire global de la même manière que dans PXSelectorAttribute .

Recommandations pour l'utilisation

Utiliser uniquement des conditions restrictives

Lorsque des limiteurs et un sélecteur sont utilisés ensemble, ce dernier ne doit pas contenir la clause IBqlWhere . Idéalement, toutes les conditions doivent être déplacées dans des restricteurs. Cette approche fournit des messages d'erreur plus conviviaux et élimine les erreurs rétroactives inutiles.

Un exemple idéal:

[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; }

Erreurs rétroactives possibles:

[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
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow