Parsware
كل المقالات

Parsware Platform

A server rule: an expense limit

A claim over 1,000 needs an approval note. The form can show that as you type, but only a plugin step makes it true, because it runs on the server whatever saved the claim. We imported a spreadsheet of claims past the form and watched two rows refused with the step's message, then looked at how one script holds the limit for both sides.

هذا المقال متاح بالإنجليزية فقط.

The Import data panel on the Claims list: Imported 2 of 4 rows, with row 3 refused as "A claim over 1000 needs an approval note." and row 4 as "A claim over 5000 needs a second approver.", next to the title "A server rule: an expense limit"

The last article ended on a gap. A form rule makes the form helpful, but an import, the API or another app never opens the form, so the rule never runs for them.

This one closes that gap for one rule: a claim over 1,000 needs an approval note, and a claim over 5,000 needs a second approver as well. The form shows it as you type. The server makes it true.

We used the Expense Limit sample; import it from samples/solutions/business-rules-expense-limit-solution-script to follow along. It adds a Claims — Shared Script app.

On the form

We started a new claim and typed an amount of 1200.50. Approval note appeared, required:

A new claim for Team offsite - venue deposit with an Amount of 1200.50, and an Approval section with a required Approval note

At 7500, Second approver appeared too:

The same claim at 7500, with both Approval note and Second approver required

That's two form rules, one running when the form opens and one when the amount changes, just like the expense approval. Useful, and skippable.

Past the form

To save claims without the form, we used the Claims list's Import data with a small CSV:

Description Amount Approval note
Taxi to the airport 64.20
Conference booth 1500
New laptops for the team 7200 Budgeted in Q3
Printer paper 38.90

The booth is over 1,000 with no note. The laptops have a note but are over 5,000 with no second approver. Neither would get past the form. The import doesn't use the form.

The Import data panel: Imported 2 of 4 rows. 2 did not import. Row 3: A claim over 1000 needs an approval note. Row 4: A claim over 5000 needs a second approver. Only Printer paper and Taxi to the airport are in the list behind it

Two rows imported, two refused, each with a sentence saying why. The row numbers are the file's lines, with the headings as line 1. Download the rows that failed gives you just those rows to fix and import again.

The plugin steps

What refused them is two plugin steps. In the maker portal we opened the solution and chose Plugin steps:

The Plugin steps list: Refuse a new claim over the limit without its approvals, on Claim, Create, Before the write, Inside the write; and Refuse a changed claim over the limit without its approvals, on Claim, Update, Before the write, Inside the write

A step names a table, a write and a stage. These two run before the write, so a refusal means nothing is stored. There's one for Create and one for Update, because a step watches one kind of write, and a claim edited later must obey the limit too. On an update, the step sees the stored record with the changes on top, so it knows the whole claim, not just the fields that changed.

We selected the first and chose Edit code:

The step's code: import missingApproval from par_claim_limits, then if the answer isn't empty, throwValidationError with it

import missingApproval from 'par_claim_limits';

var problem = missingApproval();
if (!isEmpty(problem)) {
  throwValidationError(problem);
}

throwValidationError refuses the save and sends its sentence back to whoever saved: the import panel, the form's message bar or the API's response. The step doesn't decide anything itself. It asks the same script the form uses.

One limit, two scripts

The limits live in one solution script, Claim limits:

The Claim limits script: missingApproval checks needsSecondApprover and needsApproval against the amount and returns a message built from approvalLimit, and the export line

func approvalLimit(): number {
  return 1000;
}

func needsApproval(amount: number): bool {
  return amount > approvalLimit();
}

func needsSecondApprover(amount: number): bool {
  return amount > approvalLimit() * 5;
}

func missingApproval(): string {
  var amount = claimAmount();
  if (needsSecondApprover(amount) && isEmpty(getValue('par_second_approver'))) {
    return `A claim over ${approvalLimit() * 5} needs a second approver.`;
  }
  if (needsApproval(amount) && isEmpty(getValue('par_approval_note'))) {
    return `A claim over ${approvalLimit()} needs an approval note.`;
  }
  return '';
}

export claimAmount, missingApproval, needsApproval, needsSecondApprover;

(claimAmount reads the amount and treats an empty field as 0.)

The form doesn't import this script directly. It imports a second one, Claim form, which imports Claim limits and does the showing:

import 'par_claim_limits';

func applyLimits() {
  var amount = claimAmount();
  var over = needsApproval(amount);
  setVisible('par_approval_note', over);
  setRequired('par_approval_note', over);
  var large = needsSecondApprover(amount);
  setVisible('par_second_approver', large);
  setRequired('par_second_approver', large);
}

export applyLimits;

Why two? Because the steps run on the server, and the server has no form. setVisible and setRequired don't exist there. A script a step imports may call only what a step can call, so the split is along that line:

  • Claim limits uses getValue and isEmpty, which both sides have. The form script and the steps import it.
  • Claim form uses setVisible and setRequired. Only the form rules import it.

The editor holds you to this. When the sample kept everything in one script, the step's editor underlined its import with Errors found in module 'par_claim_limits': the step was importing a script that called form-only functions. We split it while writing this article.

Change it once

Open Claim limits and change return 1000; to return 250;, then save. The form now asks for a note over 250, the steps refuse over 250, and their message says "over 250", because the message is built from the same function. One line, four places.

Try it

Import the sample and the four-row CSV above. Then fix the two failed rows, by adding a note and a second approver, and import just those. Then try the API: post a claim for 2000 with no note to /data/par_claim and read the refusal.

Next: adding a server validation rule of your own, from the New button to a refused save.