Buscar..


Atributo PXFormula

Descripción general

Una fórmula en Acumatica es un campo DAC que se calcula basándose en los valores de otros campos de objeto.

Para calcular una fórmula, el marco de Aumumatiña proporciona un conjunto de varias operaciones y funciones (como operaciones aritméticas, lógicas y de comparación y procesamiento de cadenas; consulte la Lista de fórmulas comunes incorporadas ). Además de los valores de campo, una fórmula puede usar varias constantes proporcionadas por el núcleo de Acumatica y las soluciones de la aplicación. Además, una fórmula puede obtener valores para el cálculo no solo del registro actual sino también de otras fuentes (consulte Contexto de la fórmula y sus modificadores ).

La belleza de las fórmulas es que volverán a calcular automáticamente el valor en el momento adecuado:

  • En la FieldDefaulting campo (insertar una nueva fila; controlador de eventos FieldDefaulting del campo de fórmula)
  • Al actualizar los campos dependientes (controlador de eventos FieldUpdated de cada campo dependiente)
  • En la selección de la base de datos (solo para campos no RowSelecting ; RowSelecting event handler)
  • En la base de datos, persistir si es necesario (el desarrollador debe especificarlo explícitamente; RowPersisted eventos RowPersisted )

El recálculo de un valor de campo de fórmula en la actualización de un campo dependiente genera un evento FieldUpdated para el campo de fórmula. Esto le permite hacer una cadena de fórmulas dependientes (consulte Referencias circulares directas y mediadas en fórmulas).

Los desarrolladores de aplicaciones pueden escribir sus propias fórmulas del lado de la aplicación.

Modos de uso

Una fórmula se puede utilizar en tres modos principales:

  • Simplemente calculando el valor y asignándolo al campo de fórmula (consulte Uso básico )
  • Calcular el valor agregado a partir de los valores existentes de los campos de fórmula y asignarlo a un campo especificado en el objeto principal (ver Uso agregado )
  • Modo mixto: calcular el valor de la fórmula, asignarlo al campo de la fórmula, calcular el valor agregado y asignarlo al campo en el objeto principal (ver Uso combinado )

Hay otro modo auxiliar, fórmulas no vinculadas, que es muy similar al modo mixto, pero los valores calculados de la fórmula no se asignan al campo de fórmula. El valor agregado se calcula de inmediato y se asigna al campo del objeto principal. Consulte Uso de fórmulas sin restricciones para obtener más información.

Propiedades de PXFormulaAttribute y parámetros del constructor

La funcionalidad de la fórmula es implementada por PXFormulaAttribute . El constructor de PXFormulaAttribute tiene las siguientes firmas:

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

El único parámetro formulaType es un tipo de expresión de fórmula para calcular el valor de campo de otros campos del mismo registro de datos. Este parámetro debe cumplir una de las siguientes condiciones:

  • Debe implementar la interfaz IBqlField
  • Debe ser una constante BQL
  • Debe implementar la interfaz IBqlCreator (ver Lista de fórmulas comunes incorporadas )
public PXFormulaAttribute(Type formulaType, Type aggregateType)
{
    // ...
}

El primer parámetro, formulaType , es el mismo que en el primer constructor. El segundo parámetro, aggregateType , es un tipo de fórmula de agregación para calcular el campo de registro de datos principal a partir de los campos de registro de datos secundarios. Se puede usar una función de agregación, como SumCalc, CountCalc, MinCalc y MaxCalc. Los desarrolladores de aplicaciones pueden crear sus propias fórmulas de agregación.

Un tipo de fórmula agregada debe ser un tipo genérico y debe implementar la interfaz IBqlAggregateCalculator . El primer parámetro genérico del tipo de fórmula agregada debe implementar la interfaz IBqlField y debe tener el tipo de campo del objeto principal.

public virtual bool Persistent { get; set; }

La propiedad PXFormulaAttribute.Persistent indica si el atributo vuelve a calcular la fórmula después de guardar los cambios en la base de datos. Es posible que necesite un nuevo cálculo si los campos de los que depende la fórmula se actualizan en el evento RowPersisting . Por defecto, la propiedad es igual a false .

Uso

En la mayoría de los casos, las fórmulas se utilizan para el cálculo directo del valor del campo de fórmula de otros campos del mismo registro de datos.

El ejemplo más simple de uso de fórmula:

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

En este ejemplo, el valor del campo ReceiptDate se asigna al campo DepreciateFromDate en la inserción de un nuevo registro y en la actualización del campo ReceiptDate.

Un ejemplo un poco más complejo:

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

Aquí, el saldo no aplicado del documento se calcula como la diferencia entre el saldo del documento y la cantidad aplicada.

Ejemplo de opción múltiple con un valor predeterminado:

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

Orden de los campos

El orden de los campos en el DAC es importante para corregir el cálculo de la fórmula. Todos los campos de origen (a partir de los cuales se calcula la fórmula), incluidas otras fórmulas, deben definirse en el DAC antes del campo de fórmula. De lo contrario, el campo se puede calcular incorrectamente o puede causar un error de tiempo de ejecución.

Contexto de la fórmula y sus modificadores

De forma predeterminada, el contexto del cálculo de fórmula está restringido por el objeto actual (registro) de la clase que contiene la declaración de fórmula. También se permite usar constantes (descendientes de la clase Constant<> ).

Una fórmula que usa solo los campos de su objeto:

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

Sin embargo, es posible obtener valores de entrada para el cálculo de la fórmula de otras fuentes:

  • Un registro actual de cualquier caché en el BLC (si está asignado).
  • Un registro extranjero especificado por PXSelectorAttribute .
  • Un registro padre especificado por PXParentAttribute .

La fórmula soporta los siguientes modificadores de contexto.

Current<TRecord.field> y Current2<TRecord.field>

Obtiene el valor del campo del registro almacenado en la propiedad Current de la caché de TRecord.

Si la propiedad Current del caché o el campo en sí contiene nulo:

  • Actual <> fuerza el valor predeterminado del campo y devuelve el valor predeterminado del campo.
  • Current2 <> devuelve null.

Ejemplo:

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

Obtiene el valor del campo del registro de datos principal, según lo definido por PXParentAttribute que reside en el DAC actual.

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>

Devuelve true si la tabla de DB correspondiente al DAC especificado no contiene registros, de lo contrario es 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>

Obtiene un PXSelectorAttribute definido en el campo de clave foránea (KeyField) del DAC actual.

Obtiene el registro de datos externos actualmente referenciados por el selector.

Calcula y devuelve una expresión en ese registro de datos como lo define 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; }
}

Uso de fórmulas en los campos sin límites

Si el campo de fórmula es un campo no PXFieldAttribute marcado con uno de los descendientes de PXFieldAttribute (como PXIntAttribute o PXStringAttribute ), entonces su cálculo se activa adicionalmente durante el evento RowSelecting .

Lista de fórmulas comunes incorporadas

TBD

Referencias circulares directas y mediadas en fórmulas

TBD

Flujo de control en fórmulas condicionales

TBD

Uso de múltiples fórmulas en un campo

TBD

Atributo de PXRestrictor

Introducción

El atributo PXSelectorAttribute (también conocido como el selector), aunque es vital y se usa con frecuencia, tiene, sin embargo, dos inconvenientes principales:

  • Da un mensaje no informativo "<object_name> cannot be found in the system" si no se encuentran elementos que satisfagan la condición del selector.
  • El mensaje genera el mismo mensaje de error si actualiza otros campos del registro, pero el objeto al que hace referencia el selector ya ha cambiado y ya no cumple con su condición. Este comportamiento es claramente erróneo porque la ley no debe ser retroactiva.

El PXRestrictorAttribute (también conocido como el restrictor) se puede utilizar para resolver estos problemas.

Detalles

PXRestrictorAttribute no funciona solo; siempre debe estar emparejado con un PXSelectorAttribute . Usar el restrictor sin el selector no tendrá efecto.

El restrictor encuentra el selector en el mismo campo, inyectando en él una condición adicional y el mensaje de error correspondiente. La condición del restrictor se agrega a la condición del selector a través de un AND booleano, y se genera un mensaje de error apropiado si el objeto al que se hace referencia viola la restricción del restrictor. Además, si el objeto al que se hace referencia ha cambiado y ya no cumple con la condición de restricción, no se producen mensajes de error cuando cambia cualquier otro campo del objeto de referencia.

Uso general:

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

Se pueden usar múltiples restrictores con un atributo selector. En este caso, todas las condiciones de restricción adicionales se aplican en un orden no determinado. Una vez que se viola cualquier condición, se genera el mensaje de error apropiado.

La condición Where<> del propio selector se aplica después de todas las condiciones del restrictor.

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

Opciones

El constructor de PXRestrictorAttribute toma tres parámetros:

  1. Condición adicional del restrictor. Este tipo de BQL debe implementar la interfaz IBqlWhere .
  2. El mensaje de error apropiado. El mensaje puede contener elementos de formato (llaves) para mostrar el contexto. El mensaje debe ser una constante de cadena definida en una clase estática localizable (como PX.Objects.GL.Messages ).
  3. Una matriz de tipos de campo. Estos campos deben pertenecer al objeto actual y deben implementar la interfaz IBqlField . Los valores de los campos se utilizarán para el formato de los mensajes de error.

Además, hay varias opciones que especifican el comportamiento del restrictor.

Anulación de los limitadores heredados

La propiedad ReplaceInherited indica si el restrictor actual debe anular los restrictores heredados. Si esta propiedad se establece en verdadero, se reemplazarán todos los restrictores heredados (ubicados en cualquier atributo agregado o atributo base).

Reemplazo de restrictores heredados:

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

Tenga en cuenta que no recomendamos que utilice la propiedad ReplaceInherited en el código de la aplicación cuando existan alternativas razonables. Esta propiedad está destinada principalmente para ser utilizada en personalizaciones.

Caching global

CacheGlobal admite la funcionalidad de diccionario global de la misma manera que en PXSelectorAttribute .

Recomendaciones para el uso

Utilizar solo condiciones de restricción

Cuando los restrictores y un selector se usan juntos, este último no debe contener la cláusula IBqlWhere . Idealmente, todas las condiciones deberían ser movidas a restrictores. Este enfoque proporciona mensajes de error más fáciles de usar y elimina errores retroactivos innecesarios.

Un ejemplo ideal:

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

Posibles errores retroactivos:

[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
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow