Szukaj…


Atrybut PXFormula

Ogólny opis

Formuła w programie Acumatica to pole DAC obliczane na podstawie wartości innych pól obiektowych.

Aby obliczyć formułę, środowisko Aсumatiсa zapewnia zestaw różnych operacji i funkcji (takich jak operacje arytmetyczne, logiczne i porównawcze oraz funkcje przetwarzania ciągów; patrz Lista wbudowanych wspólnych formuł ). Oprócz wartości pól formuła może wykorzystywać różne stałe dostarczane zarówno przez rdzeń Acumatica, jak i rozwiązania aplikacyjne. Ponadto formuła może uzyskiwać wartości do obliczeń nie tylko z bieżącego rekordu, ale także z innych źródeł (patrz kontekst formuły i jego modyfikatory ).

Piękno formuł polega na tym, że automatycznie przeliczą wartość we właściwym czasie:

  • FieldDefaulting pola (wstawienie nowego wiersza; FieldDefaulting obsługi zdarzeń FieldDefaulting pola formuły)
  • Po aktualizacji pól zależnych ( FieldUpdated obsługi zdarzeń FieldUpdated każdego pola zależnego)
  • Przy wyborze bazy danych (tylko dla niezwiązanych pól; RowSelecting obsługi zdarzeń RowSelecting )
  • Baza danych trwa w razie potrzeby (programista powinien to wyraźnie określić; RowPersisted obsługi zdarzeń RowPersisted )

Ponowne obliczenie wartości pola formuły po aktualizacji pola zależnego wywołuje zdarzenie FieldUpdated dla pola formuły. Umożliwia to utworzenie łańcucha zależnych formuł (patrz Bezpośrednie i pośrednie odwołania okólne we wzorach).

Twórcy aplikacji mogą pisać własne formuły po stronie aplikacji.

Tryby użytkowania

Formuły można używać w trzech głównych trybach:

  • Wystarczy obliczyć wartość i przypisać ją do pola formuły (patrz Podstawowe użycie )
  • Obliczanie wartości zagregowanej na podstawie istniejących wartości pól formuł i przypisywanie jej do określonego pola w obiekcie nadrzędnym (patrz Agregacja użycia )
  • Tryb mieszany: obliczanie wartości formuły, przypisywanie jej do pola formuły, obliczanie wartości zagregowanej i przypisywanie jej do pola w obiekcie nadrzędnym (patrz Łączone użycie )

Istnieje inny tryb pomocniczy, niezwiązane formuły, który jest bardzo podobny do trybu mieszanego, ale obliczone wartości formuły nie są przypisywane do pola formuły. Zagregowana wartość jest obliczana natychmiast i przypisywana do pola obiektu nadrzędnego. Aby uzyskać więcej informacji, zobacz Korzystanie z formuł niezwiązanych .

PXFormulaAttribute Właściwości i parametry konstruktora

Funkcja formuły jest implementowana przez PXFormulaAttribute . Konstruktor PXFormulaAttribute ma następujące podpisy:

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

Pojedynczy parametr formulaType jest rodzajem wzoru ekspresji obliczyć wartość pola z innych polach tego samego zestawu danych. Ten parametr musi spełniać jeden z następujących warunków:

  • Musi implementować interfejs IBqlField
  • Musi być stałą BQL
  • Należy zaimplementować interfejs IBqlCreator (patrz Lista wbudowanych wspólnych formuł )
public PXFormulaAttribute(Type formulaType, Type aggregateType)
{
    // ...
}

Pierwszy parametr, formulaType , jest taki sam jak w pierwszym konstruktorze. Drugi parametr, aggregateType , jest rodzajem formuły agregacyjnej do obliczania pola rekordu danych nadrzędnych na podstawie pól rekordu danych podrzędnych. Można użyć funkcji agregacji, takiej jak SumCalc, CountCalc, MinCalc i MaxCalc. Twórcy aplikacji mogą tworzyć własne formuły agregacji.

Typ formuły zagregowanej musi być typem ogólnym i musi implementować interfejs IBqlAggregateCalculator . Pierwszy parametr ogólny typu formuły agregującej musi implementować interfejs IBqlField i musi mieć typ pola obiektu nadrzędnego.

public virtual bool Persistent { get; set; }

Właściwość PXFormulaAttribute.Persistent wskazuje, czy atrybut ponownie PXFormulaAttribute.Persistent formułę po zapisaniu zmian w bazie danych. Może być konieczne ponowne obliczenie, jeśli pola, od których zależy formuła, zostaną zaktualizowane w zdarzeniu RowPersisting . Domyślnie właściwość jest równa false .

Stosowanie

W większości przypadków formuły są używane do bezpośredniego obliczania wartości pola formuły z innych pól tego samego rekordu danych.

Najprostszy przykład użycia formuły:

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

W tym przykładzie wartość pola ReceiptDate jest przypisywana do pola DepreciateFromDate po wstawieniu nowego rekordu i po aktualizacji pola ReceiptDate.

Nieco bardziej złożony przykład:

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

W tym przypadku niewykorzystane saldo dokumentu jest obliczane jako różnica między saldem dokumentu a zastosowaną kwotą.

Przykład wielokrotnego wyboru z wartością domyślną:

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

Kolejność pól

Kolejność pól w DAC jest ważna dla poprawnego obliczenia formuły. Wszystkie pola źródłowe (z których obliczana jest formuła), w tym inne formuły, muszą zostać zdefiniowane w DAC przed polem formuły. W przeciwnym razie pole może zostać obliczone niepoprawnie lub spowodować błąd w czasie wykonywania.

Kontekst formuły i jego modyfikatory

Domyślnie kontekst obliczania formuły jest ograniczony przez bieżący obiekt (rekord) klasy zawierającej deklarację formuły. Dozwolone jest również stosowanie stałych (potomków klasy Constant<> ).

Formuła, która wykorzystuje tylko pola swojego obiektu:

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

Możliwe jest jednak uzyskanie wartości wejściowych do obliczenia formuły z innych źródeł:

  • Bieżący rekord dowolnej pamięci podręcznej w BLC (jeśli jest przypisany).
  • Rekord obcy określony przez PXSelectorAttribute .
  • Rekord nadrzędny określony przez PXParentAttribute .

Formuła obsługuje następujące modyfikatory kontekstu.

Current<TRecord.field> i Current2<TRecord.field>

Pobiera wartość pola z rejestru przechowywanych w Current własności cache TRecord.

Jeśli właściwość Current pamięci podręcznej lub samo pole zawiera null:

  • Bieżące <> wymusza domyślne ustawienie pola i zwraca domyślną wartość pola.
  • Current2 <> zwraca null.

Przykład:

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

Pobiera wartość pola z nadrzędnego rekordu danych zdefiniowanego przez PXParentAttribute znajdującego się w bieżącym DAC.

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>

Zwraca true jeśli tabela DB odpowiadająca podanemu DAC nie zawiera żadnych rekordów, w przeciwnym razie 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>

Pobiera PXSelectorAttribute zdefiniowany w polu klucza obcego (KeyField) bieżącego DAC.

Pobiera obcy rekord danych, do którego aktualnie odwołuje się selektor.

Oblicza i zwraca wyrażenie na tym rekordzie danych zdefiniowanym przez 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; }
}

Używanie formuł w polach niezwiązanych

Jeśli pole formuły jest polem niezwiązanym oznaczonym jednym z potomków PXFieldAttribute (takich jak PXIntAttribute lub PXStringAttribute ), wówczas jego obliczenie jest dodatkowo uruchamiane podczas zdarzenia RowSelecting .

Lista wbudowanych wspólnych formuł

TBD

Odnośniki bezpośrednie i pośrednie w okólnikach w formułach

TBD

Kontroluj przepływ w formułach warunkowych

TBD

Używanie wielu formuł w jednym polu

TBD

Atrybut PXRestrictor

Wprowadzenie

Atrybut PXSelectorAttribute (zwany również selektorem), chociaż jest niezbędny i często używany, ma jednak dwie główne wady:

  • Daje nieinformacyjny komunikat "<object_name> cannot be found in the system" jeśli nie znaleziono żadnych elementów spełniających warunek wyboru.
  • Powoduje wygenerowanie tego samego komunikatu o błędzie, jeśli zaktualizujesz inne pola rekordu, ale obiekt, do którego odwołuje się selektor, już się zmienił i nie spełnia już swojego warunku. Takie zachowanie jest oczywiście błędne, ponieważ prawo nie może działać wstecz.

PXRestrictorAttribute (zwany również ogranicznikiem) może być wykorzystany do rozwiązania tych problemów.

Detale

PXRestrictorAttribute nie działa sam; zawsze powinien być sparowany z PXSelectorAttribute . Użycie ogranicznika bez selektora nie przyniesie żadnego efektu.

Ogranicznik znajduje selektor w tym samym polu, wprowadzając do niego dodatkowy warunek i odpowiedni komunikat o błędzie. Warunek ogranicznika jest dołączany do warunku selektora za pomocą logicznego logicznego AND i generowany jest odpowiedni komunikat o błędzie, jeśli obiekt odniesienia narusza ograniczenie ogranicznika. Ponadto, jeśli obiekt, do którego istnieje odwołanie, zmienił się i nie spełnia już warunku ograniczenia, nie zostaną wyświetlone żadne komunikaty o błędach po zmianie jakichkolwiek innych pól obiektu odniesienia.

Ogólne zastosowanie:

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

Z jednym atrybutem selektora można używać wielu ograniczników. W takim przypadku wszystkie dodatkowe warunki ograniczające są stosowane w nieokreślonej kolejności. Po naruszeniu dowolnego warunku generowany jest odpowiedni komunikat o błędzie.

Warunek Where<> samego selektora jest stosowany po wszystkich warunkach ogranicznika.

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

Opcje

Konstruktor PXRestrictorAttribute przyjmuje trzy parametry:

  1. Dodatkowy warunek ogranicznika. Ten typ BQL musi implementować interfejs IBqlWhere .
  2. Odpowiedni komunikat o błędzie. Wiadomość może zawierać elementy formatu (nawiasy klamrowe), aby pokazać kontekst. Komunikat musi być stałą ciągu zdefiniowaną w lokalizowalnej klasie statycznej (takiej jak PX.Objects.GL.Messages ).
  3. Tablica typów pól. Te pola muszą należeć do bieżącego obiektu i muszą implementować interfejs IBqlField . Wartości pól zostaną wykorzystane do formatowania komunikatów o błędach.

Ponadto istnieje kilka opcji określających zachowanie ogranicznika.

Przesłanianie odziedziczonych ograniczeń

Właściwość ReplaceInherited wskazuje, czy bieżący ogranicznik powinien zastąpić odziedziczone ograniczniki. Jeśli ta właściwość ma wartość true, wszystkie odziedziczone ograniczenia (umieszczone na dowolnych atrybutach agregujących lub atrybucie podstawowym) zostaną zastąpione.

Zastępowanie odziedziczonych ograniczników:

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

Należy pamiętać, że nie zalecamy korzystania z właściwości ReplaceInherited w kodzie aplikacji, jeśli istnieją rozsądne alternatywy. Ta właściwość jest przeznaczona przede wszystkim do dostosowywania.

Globalne buforowanie

CacheGlobal obsługuje funkcje słownika globalnego w taki sam sposób, jak w PXSelectorAttribute .

Zalecenia dotyczące korzystania

Używaj tylko warunków ograniczających

Gdy ograniczniki i selektor są używane razem, ten ostatni nie powinien zawierać klauzuli IBqlWhere . Idealnie wszystkie warunki powinny zostać przeniesione na ograniczniki. Takie podejście zapewnia bardziej przyjazne dla użytkownika komunikaty o błędach i eliminuje niepotrzebne błędy z mocą wsteczną.

Idealny przykład:

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

Możliwe błędy z mocą wsteczną:

[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
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow