Поиск…


Атрибут PXFormula

Общее описание

Формула в Acumatica - это поле DAC, которое вычисляется на основе значений других полей объекта.

Чтобы вычислить формулу, структура Aсumatica предоставляет набор различных операций и функций (таких как арифметические, логические и сравнительные операции и функции обработки строк, см. Список встроенных общих формул ). В дополнение к значениям полей формула может использовать различные константы, обеспечиваемые как ядром Acumatica, так и прикладными решениями. Более того, формула может получать значения для расчета не только из текущей записи, но и из других источников (см. Формульный контекст и его модификаторы ).

Красота формул заключается в том, что они автоматически пересчитают значение в нужное время:

  • В поле по умолчанию (вставка новой строки, FieldDefaulting события FieldDefaulting поля формулы)
  • При обновлении зависимых полей (обработчик обработчиков FieldUpdated каждого зависимого поля)
  • Выбор базы данных (только для несвязанных полей; RowSelecting обработчик событий)
  • При сохранении базы данных при необходимости (разработчик должен явно указывать ее, обработчик событий RowPersisted )

Пересчет значения поля формулы при обновлении зависимого поля вызывает событие FieldUpdated для поля формулы. Это позволяет создать цепочку зависимых формул (см. Прямые и опосредованные циркулярные ссылки в формулах).

Разработчики приложений могут писать собственные формулы приложения.

Способы использования

Формула может использоваться в трех основных режимах:

  • Просто вычислите значение и присвоите его полю формулы (см. Основное использование )
  • Вычисление суммарного значения из существующих значений полей формулы и назначение его указанному полю в родительском объекте (см. Агрегатное использование )
  • Смешанный режим: вычисление значения формулы, присвоение его полю формулы, вычисление совокупного значения и присвоение его полю в родительском объекте (см. Комбинированное использование )

Существует еще один вспомогательный режим, несвязанные формулы, который очень похож на смешанный режим, но вычисленные значения формулы не присваиваются полю формулы. Совокупное значение вычисляется немедленно и присваивается полю родительского объекта. Дополнительную информацию см. В разделе Использование несвязанных формул .

Свойства PXFormulaAttribute и параметры конструктора

Функциональность формулы реализована PXFormulaAttribute . Конструктор PXFormulaAttribute имеет следующие подписи:

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

Единственный параметр formulaType - тип выражения формулы для вычисления значения поля из других полей одной и той же записи данных. Этот параметр должен удовлетворять одному из следующих условий:

  • Необходимо реализовать интерфейс IBqlField
  • Должна быть константа BQL
  • Необходимо реализовать интерфейс IBqlCreator (см. Список встроенных общих формул )
public PXFormulaAttribute(Type formulaType, Type aggregateType)
{
    // ...
}

Первый параметр, formulaType , тот же, что и в первом конструкторе. Второй параметр, aggregateType , является типом формулы агрегации для вычисления поля записи родительских данных из полей записи дочерних данных. Может использоваться функция агрегации, такая как SumCalc, CountCalc, MinCalc и MaxCalc. Разработчики приложений могут создавать собственные формулы агрегации.

Тип агрегатной формулы должен быть общим типом и должен реализовывать интерфейс IBqlAggregateCalculator . Первый общий параметр типа обобщенной формулы должен реализовывать интерфейс IBqlField и должен иметь тип поля родительского объекта.

public virtual bool Persistent { get; set; }

Свойство PXFormulaAttribute.Persistent указывает, будет ли атрибут пересчитывать формулу после сохранения изменений в базе данных. Вам может потребоваться пересчет, если поля, на которые зависит формула, обновляются в событии RowPersisting . По умолчанию свойство равно false .

использование

В большинстве случаев формулы используются для прямого вычисления значения поля формулы из других полей одной и той же записи данных.

Простейший пример использования формулы:

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

В этом примере значение поля ReceiptDate присваивается полю DepreciateFromDate при вставке новой записи и обновлении поля ReceiptDate.

Несколько более сложный пример:

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

Здесь непримененный остаток документа рассчитывается как разница между балансом документа и применяемой суммой.

Пример множественного выбора со значением по умолчанию:

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

Порядок полей

Порядок полей в ЦАП важен для корректировки расчета формулы. Все поля источника (из которых рассчитывается формула), включая другие формулы, должны быть определены в ЦАП до поля формулы. В противном случае поле может быть вычислено неправильно или может вызвать ошибку времени выполнения.

Контекст формулы и ее модификаторы

По умолчанию контекст вычисления формулы ограничен текущим объектом (записью) класса, содержащего объявление формулы. Также разрешено использовать константы (потомки класса Constant<> ).

Формула, которая использует только поля своего объекта:

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

Тем не менее, можно получить входные значения для расчета формулы из других источников:

  • Текущая запись любого кеша в BLC (если назначена).
  • Иностранная запись, указанная PXSelectorAttribute .
  • Родительская запись, указанная PXParentAttribute .

Формула поддерживает следующие модификаторы контекста.

Current<TRecord.field> и Current2<TRecord.field>

Выбирает значение поля из записи, хранящейся в Current свойстве кеша TRecord.

Если Current свойство кэша или само поле содержит null:

  • Current <> принудительно отключает поле и возвращает значение поля по умолчанию.
  • Current2 <> возвращает null.

Пример:

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

Выбирает значение поля из родительской записи данных, как определено PXParentAttribute, находящейся на текущем ЦАП.

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>

Возвращает true если таблица DB, соответствующая указанному ЦАП, не содержит записей, иначе 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>

Выбирает атрибут PXSelectorAttribute, определенный в поле внешнего ключа (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; }
}

Использование формул для несвязанных полей

Если поле формулы является несвязанным полем, помеченным одним из потомков PXFieldAttribute (например, PXIntAttribute или PXStringAttribute ), то его вычисление дополнительно запускается во RowSelecting события RowSelecting .

Список встроенных общих формул

TBD

Прямые и опосредованные циркулярные ссылки в формулах

TBD

Контрольный поток в условных формулах

TBD

Использование нескольких формул на одном поле

TBD

Атрибут PXRestrictor

Вступление

Атрибут PXSelectorAttribute (также называемый селектором), хотя и жизненно важный и часто используемый, имеет, однако, два основных недостатка:

  • Он дает неинформируемое сообщение: "<object_name> cannot be found in the system" если не найдено элементов, удовлетворяющих условию выбора.
  • Выдает такое же сообщение об ошибке, если вы обновляете другие поля записи, но объект, на который ссылается селектор, уже изменился и больше не отвечает его условиям. Такое поведение явно неверно, потому что закон не должен быть обратным.

PXRestrictorAttribute (также называемый ограничителем) может использоваться для решения этих проблем.

подробности

PXRestrictorAttribute не работает в одиночку; он всегда должен быть сопряжен с PXSelectorAttribute . Использование ограничителя без селектора не будет иметь никакого эффекта.

Ограничитель находит селектор в том же поле, вводя в него дополнительное условие и соответствующее сообщение об ошибке. Условие ограничителя добавляется к условию выбора через логическое И, и соответствующее сообщение об ошибке генерируется, если ссылочный объект нарушает ограничение ограничителя. Кроме того, если ссылочный объект изменился и больше не удовлетворяет условию ограничителя, при изменении любых других полей ссылочного объекта не появляются сообщения об ошибках.

Общее использование:

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

Несколько ограничителей можно использовать с одним атрибутом селектора. В этом случае все дополнительные условия ограничителя применяются в неустановленном порядке. Как только любое условие нарушено, генерируется соответствующее сообщение об ошибке.

Условие Where<> самого селектора применяется после всех условий ограничителя.

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

Опции

Конструктор PXRestrictorAttribute принимает три параметра:

  1. Дополнительное условие ограничителя. Этот тип BQL должен реализовывать интерфейс IBqlWhere .
  2. Соответствующее сообщение об ошибке. Сообщение может содержать элементы формата (фигурные скобки) для отображения контекста. Сообщение должно быть строковой константой, определенной в локализуемом статическом классе (например, PX.Objects.GL.Messages ).
  3. Массив типов полей. Эти поля должны принадлежать текущему объекту и должны реализовывать интерфейс IBqlField . Значения полей будут использоваться для форматирования сообщений об ошибках.

Кроме того, существует несколько опций, которые определяют поведение ограничителя.

Переопределение унаследованных ограничителей

Свойство ReplaceInherited указывает, должен ли текущий ограничитель переопределять унаследованные ограничители. Если для этого свойства установлено значение true, то все унаследованные ограничители (помещенные в любые агрегированные атрибуты или базовый атрибут) будут заменены.

Замена унаследованных ограничителей:

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

Обратите внимание, что мы не рекомендуем использовать свойство ReplaceInherited в коде приложения, если существуют разумные альтернативы. Это свойство в первую очередь предназначено для использования в настройках.

Глобальное кэширование

CacheGlobal поддерживает глобальную функциональность словаря так же, как в PXSelectorAttribute .

Рекомендации по использованию

Использовать только условия ограничителя

Когда ограничители и селектор используются вместе, последний не должен содержать предложение IBqlWhere . В идеале все условия должны быть перенесены в ограничители. Этот подход обеспечивает более удобные сообщения об ошибках и устраняет ненужные ретроактивные ошибки.

Идеальный пример:

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

Возможные ретроактивные ошибки:

[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
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow