Parsware
Todos los artículos

Parsware Platform

A client rule: an expense approval

A form rule runs in the browser and changes the form as you type. In the Expense Approval sample, typing "rejected" into Status shows a Rejection reason and makes it required, and approving the claim hides it again and locks the amount. Here is the form, the three rules behind it, and the one script they share.

Este artículo solo está disponible en inglés.

A new expense with Status set to rejected and a Decision section showing a required Rejection reason and an Approver note, next to the title "A client rule: an expense approval"

A form rule is the first kind of business logic most makers write. It runs in the browser, on one form, and it makes the form respond to what someone types: a field appears, becomes required, fills itself in or stops being editable.

We used the Expense Approval sample; import it from samples/solutions/business-rules-expense-approval to follow along. It adds an Expenses — Business Rules app.

The form

We opened the app and chose New. The form has a Claim section with a description, an amount, a currency and a status. Currency already says GBP: a rule filled it in when the form opened, because it was empty.

A new expense with Description, Amount, Currency set to GBP and an empty Status, and no Decision section

There's also a Decision section on this form, but you can't see it. Both of its fields are hidden while the claim is a draft, so the section has nothing to show.

Reject it

We typed a description and an amount, then rejected into Status. As soon as we left the field, the Decision section appeared with Rejection reason, marked required, and Approver note.

The same form with Status set to rejected: a Decision section appears with Rejection reason, marked with a red star, and Approver note. Amount is now read-only

Two more things changed. Amount lost its star and turned read-only: a decided claim shouldn't have its amount drift. And the rule read the value we had just typed, not the one the form opened with.

We pressed Save without a reason. The form refused, and marked the field:

The Rejection reason field outlined, with "This field is required." under it, and the form not saved

The required-ness a rule sets isn't just a star. The save checks it too.

Approve it

We changed Status to approved. Rejection reason disappeared, and stopped being required, so the claim could be saved. Approver note stayed.

Status set to approved: the Decision section shows only Approver note

The rule also emptied the reason we had typed. A reason for rejecting an approved claim would be wrong on the record.

Reopen it

That clearing has to happen only when somebody changes the status, not whenever the form opens. We saved a second claim as rejected, with a reason, and reloaded it:

A saved expense, Team lunch - quarterly review, reopened with Status rejected and its Rejection reason still filled in

The reason is still there. The next section shows how the rule tells the two moments apart.

The rules behind it

In the maker portal we opened the solution, then Tables → Expense → Forms → Expense, and chose Events:

The Events list of the Expense form: Apply the decision rules when the form opens, Apply the decision rules when the status changes, and Default the currency on a new claim

Three rules. Each one answers one moment: when the form opens, or when a field changes. (A rule can also run when the form is saved.)

The decision logic has to be right in two moments, when the form opens and whenever the status changes, so there are two rules for it. Each is two lines:

The business rule designer for Apply the decision rules when the status changes: Runs when OnChange, the field Status, and a script that imports par_expense_decisions and calls applyDecisionFields and clearReasonWhenNotRejected

The logic itself is in a solution script, Expense decision rules, which both rules import:

The Expense decision rules script: applyDecisionFields reads the status, then calls setVisible and setRequired on par_rejection_reason, setVisible on par_approver_note and setDisabled on par_amount

func applyDecisionFields() {
  var status = getValue('par_status');
  var rejected = status == 'rejected';
  setVisible('par_rejection_reason', rejected);
  setRequired('par_rejection_reason', rejected);
  setVisible('par_approver_note', status == 'approved' || rejected);
  setDisabled('par_amount', status == 'approved' || rejected);
}

func clearReasonWhenNotRejected() {
  if (eventType() == 'OnChange' && getValue('par_status') != 'rejected') {
    setValue('par_rejection_reason', '');
  }
}

eventType() is how the script tells the moments apart. Showing and hiding fields is right in both. Clearing a value is an edit, and an edit belongs to the moment somebody changed something, so it happens only on OnChange.

What a form rule can do

Function What it does
getValue, setValue Read a field, or fill it in
setVisible Show or hide a field. A hidden field isn't on the form at all
setRequired Make a field required. The star shows, and the save checks it
setDisabled Make a field read-only
eventType Which moment this is: OnLoad, OnChange or OnSave
cancelSave Stop a save, from a rule that runs OnSave

The full list is in the editor's eye button, and in what a script can see.

What a form rule can't do

A form rule changes the form. It doesn't guard the table. An import, the API, a Custom API or another app never opens this form, so none of these rules run for them. They could save a rejected claim with no reason.

That's fine for making a form pleasant, and it's the reason the next article exists: the same kind of rule, enforced on the server, where nothing can skip it.

A fix from writing this

The first time we rejected a claim, Rejection reason appeared without its red star, and the save was refused anyway. The form's save check knew the rule had made the field required; the field's label didn't. Both now ask the same question, so a field a rule makes required says so.

Try it

Import the sample and reject a claim, then approve it. Then open Expense decision rules and make Approver note required for approved claims too: one more setRequired line, and both rules pick it up.

Next: a server rule: the same kind of limit, enforced whatever saves the record.