Property Calculator Examples
Overview
Progressive, worked examples for every Property Calculator calculation mode, from simple one-line expressions to complete multi-rule Class Group configurations.
Each example includes the scenario, the expression or configuration, a step-by-step explanation, and the expected result. All examples use Calculate Expression mode unless noted otherwise.
Basic Examples
1. Simple Arithmetic — Calculate Total Price
Scenario: An invoice line has Quantity and Unit Price properties. Calculate the Line Total.
| Mode: Calculate Expression | Evaluate as Expression: ✅ |
%PROPERTY_{PD.Quantity}% * %PROPERTY_{PD.UnitPrice}%
| Property | Value |
|---|---|
| PD.Quantity | 25 |
| PD.UnitPrice | 49.90 |
| Result | 1247.50 |
Explanation: Both placeholders are resolved as numbers and multiplied.
2. Text Assembly — Build Document Title
Scenario: Automatically generate a document title from project code and document type.
| Mode: Calculate Expression | Evaluate as Expression: ❌ (simple placeholder) |
%PROPERTY_{PD.ProjectCode}% - %PROPERTY_{PD.DocumentType}% - %PROPERTY_{PD.Revision}%
| Property | Value |
|---|---|
| PD.ProjectCode | PRJ-2026 |
| PD.DocumentType | Specification |
| PD.Revision | Rev.C |
| Result | PRJ-2026 - Specification - Rev.C |
Explanation: With “Evaluate as Expression” OFF, placeholders are simply replaced with their text values. No NCalc evaluation occurs.
3. Conditional Text — Priority Label
Scenario: Display a human-readable priority label based on a numeric score.
| Mode: Calculate Expression | Evaluate as Expression: ✅ |
iif(%PROPERTY_{PD.Score}% >= 80, 'High Priority',
iif(%PROPERTY_{PD.Score}% >= 50, 'Medium Priority', 'Low Priority'))
| Score | Result |
|---|---|
| 92 | High Priority |
| 65 | Medium Priority |
| 30 | Low Priority |
Explanation: Nested iif() functions create a tiered classification. The outer iif checks ≥80 first, then falls through to the inner iif for ≥50.
4. Date Calculation — Due Date
Scenario: Set the payment due date to 30 days after the invoice date.
| Mode: Calculate Expression | Evaluate as Expression: ✅ |
dateAdd(%PROPERTY_{PD.InvoiceDate}%, 30, 'days')
| Property | Value |
|---|---|
| PD.InvoiceDate | 2026-01-15 |
| Result | 2026-02-14 |
5. Null-Safe Display — Contact Info
Scenario: Show the primary email if available, otherwise fall back to secondary email, then to “No email on file”.
| Mode: Calculate Expression | Evaluate as Expression: ✅ |
coalesce(%PROPERTY_{PD.PrimaryEmail}%, %PROPERTY_{PD.SecondaryEmail}%, 'No email on file')
| PrimaryEmail | SecondaryEmail | Result |
|---|---|---|
| john@acme.com | jane@acme.com | john@acme.com |
| (empty) | jane@acme.com | jane@acme.com |
| (empty) | (empty) | No email on file |
6. Lookup Name Check — Status-Based Flag
Scenario: Set a boolean flag indicating whether a contract is active.
| Mode: Calculate Expression | Evaluate as Expression: ✅ |
lookupName(%PROPERTY_{PD.ContractStatus}%) == 'Active'
| ContractStatus (lookup) | Result |
|---|---|
| Active (ID: 3) | true |
| Expired (ID: 5) | false |
Intermediate Examples
7. MSLU Aggregation — Invoice Total from Line Items
Scenario: An invoice object has an MSLU property Invoice Lines linking to line item objects. Each line item has an Amount property. Calculate the invoice total.
| Mode: Calculate Expression | Evaluate as Expression: ✅ |
Sum(%PROPERTY_{PD.InvoiceLines}.PROPERTY_{PD.Amount}%)
| Invoice Lines (MSLU) | Amount per line | Result |
|---|---|---|
| Line 1 → Amount: 500 | ||
| Line 2 → Amount: 1200 | ||
| Line 3 → Amount: 350 | ||
| Invoice Total | 2050 |
How it works:
%PROPERTY_{PD.InvoiceLines}%resolves to the MSLU (3 lookup items).PROPERTY_{PD.Amount}%chains to readAmountfrom each linked object- The placeholder auto-expands:
Sum([__P0], [__P1], [__P2])where P0=500, P1=1200, P2=350 Sum()returns 2050
Bonus — Invoice total with VAT:
let('subtotal', Sum(%PROPERTY_{PD.InvoiceLines}.PROPERTY_{PD.Amount}%),
Round(get('subtotal') * 1.24, 2))
Result: 2542.00
8. Regex Extraction — Order Number from Text
Scenario: A document title contains an order number in the format “ORD-NNNN”. Extract just the numeric part.
| Mode: Calculate Expression | Evaluate as Expression: ✅ |
regexMatch(%PROPERTY_{PD.Title}%, 'ORD-(\d+)')
| Title | Result |
|---|---|
| Purchase Order ORD-4521 for Acme | ORD-4521 |
To get just the number part:
regexMatchGroup(%PROPERTY_{PD.Title}%, 'ORD-(\d+)', 1)
Result: 4521
9. Switch-Based Mapping — Country to VAT Rate
Scenario: Determine VAT rate based on the customer’s country.
| Mode: Calculate Expression | Evaluate as Expression: ✅ |
switch(lookupName(%PROPERTY_{PD.Country}%),
'Finland', 0.255,
'Sweden', 0.25,
'Norway', 0.25,
'Denmark', 0.25,
'Germany', 0.19,
'France', 0.20,
'UK', 0.20,
0.00)
| Country | Result |
|---|---|
| Finland | 0.255 |
| Germany | 0.19 |
| Japan | 0.00 (default) |
10. String Assembly with Conditions — Address Block
Scenario: Build a formatted address string, handling optional fields gracefully.
| Mode: Calculate Expression | Evaluate as Expression: ✅ |
concat(
%PROPERTY_{PD.StreetAddress}%,
iif(isNullOrEmpty(%PROPERTY_{PD.ApartmentUnit}%), '', concat(', Apt ', %PROPERTY_{PD.ApartmentUnit}%)),
'\n',
%PROPERTY_{PD.City}%, ' ',
%PROPERTY_{PD.PostalCode}%,
'\n',
lookupName(%PROPERTY_{PD.Country}%))
| Fields | Result |
|---|---|
| Street: 123 Main St, Apt: 4B, City: Helsinki, Postal: 00100, Country: Finland | 123 Main St, Apt 4BHelsinki 00100Finland |
| Street: 456 Oak Ave, Apt: (empty), City: Turku, Postal: 20100, Country: Finland | 456 Oak AveTurku 20100Finland |
11. Date-Based Conditional — Contract Expiry Warning
Scenario: Set a warning text when a contract is within 30 days of expiration.
| Mode: Calculate Expression | Evaluate as Expression: ✅ |
let('daysLeft', dateDiff(today(), %PROPERTY_{PD.ContractEnd}%, 'days'),
iif(get('daysLeft') < 0, 'EXPIRED',
iif(get('daysLeft') <= 30, concat('Expires in ', get('daysLeft'), ' days'),
iif(get('daysLeft') <= 90, concat('Expires in ~', Round(get('daysLeft') / 30, 0), ' months'),
'Active'))))
| ContractEnd | Today | daysLeft | Result |
|---|---|---|---|
| 2026-04-01 | 2026-05-07 | -36 | EXPIRED |
| 2026-05-20 | 2026-05-07 | 13 | Expires in 13 days |
| 2026-07-15 | 2026-05-07 | 69 | Expires in ~2 months |
| 2026-12-31 | 2026-05-07 | 238 | Active |
12. File Count Validation — Check for Required Attachments
Scenario: Generate a status text indicating whether required PDF documents are attached.
| Mode: Calculate Expression | Evaluate as Expression: ✅ |
let('pdfCount', filecount('pdf'),
iif(get('pdfCount') == 0, '❌ No PDF attached',
iif(get('pdfCount') == 1, '✅ 1 PDF attached',
concat('✅ ', get('pdfCount'), ' PDFs attached'))))
| Files on Object | Result |
|---|---|
| Report.docx | ❌ No PDF attached |
| Invoice.pdf | ✅ 1 PDF attached |
| Invoice.pdf, Appendix.pdf, Terms.pdf | ✅ 3 PDFs attached |
Advanced Examples
13. Multi-Step Calculation with Variables — Full Invoice Computation
Scenario: Calculate subtotal, discount, tax, and total for an invoice — all in a single expression that saves the total.
| Mode: Calculate Expression | Evaluate as Expression: ✅ |
store('subtotal', Sum(%PROPERTY_{PD.InvoiceLines}.PROPERTY_{PD.Amount}%)) *0+
store('discountRate', iif(%PROPERTY_{PD.IsVIP}%, 0.10, 0.00)) *0+
store('discountedSubtotal', get('subtotal') * (1 - get('discountRate'))) *0+
store('vatRate', switch(lookupName(%PROPERTY_{PD.Country}%),
'Finland', 0.255,
'Sweden', 0.25,
'Germany', 0.19,
0.00)) *0+
store('vat', Round(get('discountedSubtotal') * get('vatRate'), 2)) *0+
Round(get('discountedSubtotal') + get('vat'), 2)
Walkthrough:
store('subtotal', ...)— sum all line item amounts → 5000store('discountRate', ...)— 10% discount for VIP customers, 0% otherwise → 0.10store('discountedSubtotal', ...)— 5000 × 0.90 → 4500store('vatRate', ...)— look up country-specific VAT → 0.255store('vat', ...)— 4500 × 0.255 → 1147.50- Final result: 4500 + 1147.50 → 5647.50
*0+ chains store() calls without letting their return values leak into the result. See Variables in the NCalc reference for how the pattern works.
14. Cross-Object Aggregation — Project Budget Utilization
Scenario: A project has an MSLU linking to tasks. Calculate budget utilization percentage from the project’s budget and task costs.
| Mode: Calculate Expression | Evaluate as Expression: ✅ |
let('totalCost', Sum(%PROPERTY_{PD.Tasks}.PROPERTY_{PD.TaskCost}%),
let('budget', %PROPERTY_{PD.ProjectBudget}%,
iif(get('budget') > 0,
concat(Round(get('totalCost') / get('budget') * 100, 1), '%'),
'No budget set')))
| Budget | Task Costs | Result |
|---|---|---|
| 100000 | 25000 + 35000 + 15000 = 75000 | 75.0% |
| 100000 | 95000 + 12000 = 107000 | 107.0% |
| 0 | 5000 | No budget set |
15. Dynamic Document Numbering — Year-Based Sequence
Scenario: Generate a document number in the format DOC-YYYY-NNNN using the current year and a sequence number property.
| Mode: Calculate Expression | Evaluate as Expression: ✅ |
concat('DOC-',
formatDate(now(), 'yyyy'),
'-',
padLeft(tostring(%PROPERTY_{PD.SequenceNumber}%), 4, '0'))
| SequenceNumber | Result |
|---|---|
| 1 | DOC-2026-0001 |
| 42 | DOC-2026-0042 |
| 1337 | DOC-2026-1337 |
16. Lookup Set Operations — Active Participants Only
Scenario: A meeting object has “All Invited” (MSLU) and “Declined” (MSLU) properties. Calculate “Confirmed Attendees” by removing declined people.
| Mode: Calculate Expression | Evaluate as Expression: ✅ |
lookupExcept(%PROPERTY_{PD.AllInvited}%, %PROPERTY_{PD.Declined}%)
| All Invited | Declined | Result |
|---|---|---|
| Alice (1), Bob (2), Carol (3), Dave (4) | Bob (2), Dave (4) | Alice (1), Carol (3) |
With attendee count:
let('confirmed', lookupExcept(%PROPERTY_{PD.AllInvited}%, %PROPERTY_{PD.Declined}%),
concat(lookupCount(get('confirmed')), ' confirmed: ', lookupNames(get('confirmed'))))
Result: 2 confirmed: Alice, Carol
17. Regex-Based Data Transformation — Phone Number Formatting
Scenario: Normalize phone numbers to international format.
| Mode: Calculate Expression | Evaluate as Expression: ✅ |
let('phone', trim(%PROPERTY_{PD.PhoneNumber}%),
iif(startsWith(get('phone'), '+'), get('phone'),
iif(startsWith(get('phone'), '0'),
concat('+358', substring(get('phone'), 1)),
concat('+358', get('phone')))))
| Input | Result |
|---|---|
| +358 40 1234567 | +358 40 1234567 |
| 040 1234567 | +35840 1234567 |
| 0401234567 | +358401234567 |
18. Complex Business Rule — SLA Compliance Check
Scenario: Determine SLA status based on response time, priority, and whether it’s a business day. The expression checks if the issue was responded to within the SLA timeframe.
| Mode: Calculate Expression | Evaluate as Expression: ✅ |
store('responseHours', dateDiff(%PROPERTY_{PD.Created}%,
ifNull(%PROPERTY_{PD.FirstResponse}%, now()), 'hours')) *0+
store('slaLimit', switch(lookupName(%PROPERTY_{PD.Priority}%),
'Critical', 4,
'High', 8,
'Medium', 24,
'Low', 72,
48)) *0+
store('isResolved', not isNull(%PROPERTY_{PD.FirstResponse}%)) *0+
iif(not get('isResolved'),
iif(get('responseHours') > get('slaLimit'),
concat('🔴 SLA BREACHED (', Round(get('responseHours'), 0), 'h / ', get('slaLimit'), 'h limit)'),
concat('🟡 Pending (', Round(get('responseHours'), 0), 'h / ', get('slaLimit'), 'h limit)')),
iif(get('responseHours') <= get('slaLimit'),
concat('🟢 Met SLA (', Round(get('responseHours'), 0), 'h / ', get('slaLimit'), 'h limit)'),
concat('🔴 SLA Missed (', Round(get('responseHours'), 0), 'h / ', get('slaLimit'), 'h limit)')))
| Priority | Created | First Response | Response Hours | SLA Limit | Result |
|---|---|---|---|---|---|
| Critical | May 5, 09:00 | May 5, 11:30 | 2.5h | 4h | 🟢 Met SLA (3h / 4h limit) |
| High | May 3, 14:00 | May 4, 10:00 | 20h | 8h | 🔴 SLA Missed (20h / 8h limit) |
| Medium | May 6, 08:00 | (not yet) | 30h | 24h | 🔴 SLA BREACHED (30h / 24h limit) |
19. FOREACH — Generate Line Item Summary
Scenario: Create a text summary listing all invoice line items with their amounts.
| Mode: Calculate Expression | Evaluate as Expression: ✅ |
%PROPERTY_{PD.InvoiceLines}.FOREACH%• %PROPERTY_{PD.Description}%: %PROPERTY_{PD.Amount}% EUR
%
Result (3 line items):
• Software License: 2500 EUR
• Consulting Services: 1200 EUR
• Training: 800 EUR
20. Combined Functions — Smart Contract Summary
Scenario: Generate a comprehensive one-line summary for a contract, combining multiple data sources and calculations.
| Mode: Calculate Expression | Evaluate as Expression: ✅ |
store('value', %PROPERTY_{PD.ContractValue}%) *0+
store('daysLeft', dateDiff(today(), %PROPERTY_{PD.EndDate}%, 'days')) *0+
store('status', lookupName(%PROPERTY_{PD.Status}%)) *0+
concat(
get('status'), ' | ',
lookupName(%PROPERTY_{PD.Customer}%), ' | ',
iif(get('value') >= 100000, '💰 ', ''),
Round(get('value'), 0), ' EUR | ',
iif(get('daysLeft') < 0,
concat('Expired ', Abs(get('daysLeft')), 'd ago'),
iif(get('daysLeft') < 30,
concat('⚠️ ', get('daysLeft'), 'd left'),
concat(Round(get('daysLeft') / 30, 0), ' months left'))),
' | ',
lookupCount(%PROPERTY_{PD.Attachments}%), ' files')
Result: Active | Acme Corp | 💰 250000 EUR | ⚠️ 18d left | 5 files
Other Calculation Mode Examples
The examples above all use Calculate Expression mode. Below are standalone examples of every other calculation mode.
Set Static Values
Scenario: When a document is moved to “Archived” status, set the Confidential property to true.
Mode: Set Static Values
Property: PD.Confidential
Value: true (Static)
Conditions:
- Type: Basic Conditions
Property PD.Status = "Archived"
The same mode also sets a property to NULL — just choose “Set to NULL” as the Value instead of a static value.
Pick Substring
Full field reference (Remove Substring from Main String, In Error Case, Pick Only Subexpression, etc.): Pick Substring settings.
Scenario: Incoming scanned documents have titles like "INV-2026-0042 Acme Corp 15.01.2026". Parse them into separate properties.
Mode: Pick Substring
Pick Substrings From: %PROPERTY_{PD.Title}%
Substrings:
┌─ Substring 1: RegExp INV-\d{4}-\d+ → Save To PD.InvoiceNumber (extracts "INV-2026-0042")
├─ Substring 2: RegExp (?<=\d{4}\s)[\w\s]+(?=\s\d{2}\.) → Save To PD.CustomerName (extracts "Acme Corp")
└─ Substring 3: RegExp \d{2}\.\d{2}\.\d{4} → Save To PD.DateText (extracts "15.01.2026")
Scenario — Named group extraction:
Pick Substrings From: %PROPERTY_{PD.Code}%
Substrings:
└─ Save To: PD.YearCode
Condition Type: Pick first founded RegExp
RegExp: PRJ-(?<value>\d{4})-[A-Z]+
Pick Only Subexpression: ✅
→ From "PRJ-2026-FIN" extracts just "2026"
Remove Property
Scenario: Remove the PD.TechnicalReviewDate property from non-technical documents.
Mode: Remove Property
Property: PD.TechnicalReviewDate
Conditions:
- Type: Advanced Conditions
Expression: lookupName(%PROPERTY_{PD.DocumentType}%) != 'Technical Report'
To remove several properties on the same condition (e.g. all approval fields when a document returns to Draft), add one Remove Property rule per property — each with the same Conditions block.
Convert Date
Full field reference (Set Timezone, Text Timezone, etc.): Convert Date settings.
Scenario: Convert a date stored as Finnish text (from imported data) into a proper DateTime property.
Mode: Convert Date
Conversion Type: String to Date
Value From: PD.ImportedDateText → "15.01.2026"
String Format: dd.MM.yyyy
Language: fi-FI
Target Property: PD.DocumentDate
→ Result: 2026-01-15T00:00:00 (DateTime)
Variant — source text includes a timezone: turn on Set Timezone and set Text Timezone (e.g. FLE Standard Time for UTC+2 Helsinki) to convert the parsed value from UTC into local time as part of the same conversion.
Scenario: Format a DateTime property as a locale-specific text for display.
Mode: Convert Date
Conversion Type: Date to String
Value From: PD.Created → 2026-05-07T14:30:00
String Format: d. MMMM yyyy 'klo' HH:mm
Language: fi-FI
Target Property: PD.CreatedText
→ Result: "7. toukokuuta 2026 klo 14:30"
Count Date Or Time
Full field reference (per-operation Increase/Decrease, Unit, Data Type, etc.): Count Date Or Time settings.
Scenario: Set a due date to 5 business days after the received date.
Mode: Count Date Or Time
Property: PD.DueDate
Base Date: PD.ReceivedDate → 2026-01-10 (Friday)
Operations:
1. Increase by 5 Business Days (Fixed)
→ Result: 2026-01-17 (Friday — skips Sat+Sun)
Scenario: Calculate warranty expiration as contract start + N months (from metadata).
Mode: Count Date Or Time
Property: PD.WarrantyExpiration
Base Date: PD.ContractStart → 2026-01-01
Operations:
1. Increase by [PD.WarrantyMonths] Months (From Metadata, PD.WarrantyMonths = 24)
→ Result: 2028-01-01
Scenario: Chained operations — project milestone with buffer.
Mode: Count Date Or Time
Property: PD.MilestoneDeadline
Base Date: PD.ProjectStart → 2026-01-15
Operations:
1. Increase by 6 Months (Fixed) → 2026-07-15
2. Decrease by 5 Business Days (Fixed) → 2026-07-08
3. Set Hour to 17 (Fixed) → 2026-07-08 17:00:00
→ Deadline is 6 months out minus 5 business days, at 5 PM
Period Length
Scenario: Calculate contract duration in days.
Mode: Period Length
Property: PD.ContractDuration
Start Date: PD.ContractStart → 2026-01-01
End Date: PD.ContractEnd → 2026-12-31
Unit: Day
Modifier: 1 (inclusive)
→ Result: 366
Scenario: Calculate processing time in hours.
Mode: Period Length
Property: PD.ProcessingHours
Start Date: PD.ReceivedTimestamp → 2026-05-07 08:00
End Date: PD.CompletedTimestamp → 2026-05-07 14:30
Unit: Hour
Modifier: 0
→ Result: 6
Filter Lookup Values
Scenario: An order’s Related Suppliers MSLU should only contain suppliers with “Active” status.
Mode: Filter Lookup Values
Property: PD.RelatedSuppliers (MSLU)
Lookup Values From: PD.RelatedSuppliers
Conditions:
- Type: Basic Conditions
Property "Status" equals "Active"
→ Suppliers with Status ≠ Active are removed from the list on each check-in
Scenario: Filter project tasks to keep only those assigned to the current user’s department.
Mode: Filter Lookup Values
Property: PD.MyDeptTasks (MSLU)
Lookup Values From: PD.AllTasks
Conditions:
- Type: Compare Properties
Property X: %PROPERTY_{PD.Department}%
Comparison: Equal
Property Y: %PROPERTY_{PD.Department}%
Read Y from Main Object: ON
→ Keeps only tasks whose Department matches the parent object's Department
Order Lookup Values
Scenario: Sort an MSLU of meeting participants alphabetically.
Mode: Order Lookup Values
Property: PD.Participants (MSLU)
Lookup Values From: PD.Participants
Order Type: Alphabetical
Reverse Order: ❌ (A → Z)
Order By: %PROPERTY_{PD.FullName}%
Amount of Lookups: 0 (keep all)
Scenario: Keep only the 3 most expensive items, sorted by price descending.
Mode: Order Lookup Values
Property: PD.TopItems (MSLU)
Lookup Values From: PD.AllItems
Order Type: Numerical
Reverse Order: ✅ (highest first)
Order By: %PROPERTY_{PD.Price}%
Amount of Lookups: 3
→ From 10 items, keeps the 3 most expensive, sorted high → low
Values From MSLU
Scenario: Collect all task descriptions from project tasks into a summary text field.
Mode: Values From MSLU
Property: PD.TaskSummary (Text)
Multi-Select Lookup: PD.ProjectTasks
Conditions for Listed Object: (none)
→ Result: "Design UI, Implement backend, Write tests, Deploy"
Scenario: Collect email addresses from active team members only.
Mode: Values From MSLU
Property: PD.TeamEmails (Text)
Multi-Select Lookup: PD.TeamMembers
Conditions for Listed Object:
- Type: Basic Conditions
Property "Status" equals "Active"
→ Result: "alice@company.com, bob@company.com"
(inactive members excluded)
Search Objects
Full field reference (Keep Previous Content, Include Deleted Objects, Add version-specific reference, etc.): Search Objects settings.
Scenario: Populate an MSLU with all invoices belonging to the same customer.
Mode: Search Objects
Property: PD.CustomerInvoices (MSLU)
Property Conditions:
- Object Type = Invoice
- PD.Customer equals %PROPERTY_{PD.Customer}%
Max Results: 50
→ Finds up to 50 invoices for the same customer (add an Additional Condition to exclude e.g. "Cancelled" ones)
Scenario — Value list item resolution by name:
Mode: Search Objects
Property: PD.Department (SSLU)
Search Value List Items: ✅
Search By: Name
Search Value: %PROPERTY_{PD.DepartmentText}%
→ Converts text "Finance" → Department lookup value "Finance" (ID: 3)
Variant — resolving multiple values at once: target an MSLU property, set Value Delimiter (e.g. ,), and give a delimited Search Value such as "Urgent,Review,Final" — each term is resolved to its own lookup value.
Create Object
Full field reference (Create as Copy, Append Text to File Names, Create in Background, etc.): Create Object settings.
Scenario: When an order is confirmed, create a single delivery note.
Mode: Create Object
Object Type: DeliveryNote
Duplicate Detection: PD.SourceOrder = (current object)
Other Property Values: PD.Customer = %PROPERTY_{PD.Customer}%, PD.Status = "Pending"
Conditions: PD.Status changes to "Confirmed"
→ Creates one DeliveryNote per confirmed order; skips if one already exists for this order
Scenario — Copy an object as a new revision:
Mode: Create Object
Create as Copy: ✅
Source Object: (current object GUID)
Properties to be Removed: PD.ApprovedBy, PD.ApprovalDate, PD.DigitalSignature
Other Property Values: PD.Status = "Draft", PD.PreviousRevision = (current object)
Conditions: PD.Status changes to "Superseded"
→ Copies the object as a new Draft revision, stripped of approval data
Scenario — Create separate line items from MSLU:
Mode: Create Object
Separate Object for Each Value Combination: ✅
Value Combinations: PD.Products → PD.Product
Other Property Values: PD.ParentOrder = (current object)
→ If PD.Products has 3 items, creates 3 separate LineItem objects
History
Scenario: Preserve the original submission date from when the document was first submitted.
Mode: History
Conditions for Source Version:
- Type: Basic Conditions
Property "PD.Status" equals "Submitted"
Copy from All Matching Versions: ❌ (first match only)
Replace Files: ❌
Throw Exception: ❌
Property Mappings:
- Source: %PROPERTY_{PD.SubmissionDate}% → Target: PD.OriginalSubmissionDate
→ Reads the SubmissionDate from the first version where Status was "Submitted"
Scenario: Restore approved version’s files.
Mode: History
Conditions for Source Version:
- Type: Basic Conditions
Property "PD.Status" equals "Approved"
Replace Files: ✅
Throw Exception: ✅
→ Overwrites current files with files from the approved version
→ Throws an error if no approved version exists
File Operation
Scenario: Prefix all filenames with the document number.
Mode: File Operation
Operation: PrefixPostfix
Prefix: %PROPERTY_{PD.DocumentNumber}% -
Postfix: (empty)
Always Add: ❌
→ "Report.pdf" → "DOC-2026-0042 - Report.pdf"
→ On next check-in, prefix is already there → no change
Scenario: Add revision code as postfix to filenames.
Mode: File Operation
Operation: PrefixPostfix
Prefix: (empty)
Postfix: _Rev%PROPERTY_{PD.Revision}%
Always Add: ❌
→ "Drawing.dwg" → "Drawing_RevC.dwg"
Send Email
Scenario: Notify the project manager when a document is approved.
Mode: Send Email
Allow Email Sending: ✅
To: %PROPERTY_{PD.ProjectManager}.PROPERTY_{PD.Email}%
Subject: ✅ Approved: %OBJTITLE%
Body: <h2>Document Approved</h2>
<p><b>%OBJTITLE%</b> has been approved.</p>
<p>Approved by: %PROPERTY_{PD.ApprovedBy}%</p>
<p>Date: %PROPERTY_{PD.ApprovalDate}%</p>
<p>Value: %PROPERTY_{PD.ContractValue}% EUR</p>
Conditions:
- Type: Changed Propertyvalues
Properties: PD.Status
- Type: Basic Conditions
Property PD.Status = "Approved"
To trigger on a computed condition instead of a simple status match (e.g. “invoice more than 14 days overdue”), use an Advanced Conditions entry with an NCalc expression such as dateDiff(%PROPERTY_{PD.DueDate}%, today(), 'days') > 14.
Grouping Level
Scenario: Group all line item calculations under a shared condition.
Mode: Grouping Level
Name: "Line Item Financial Calculations"
Conditions:
- Type: Changed Propertyvalues
Properties: PD.Quantity, PD.UnitPrice, PD.DiscountPercent
Nested Properties:
┌─ Rule 1: "Gross Amount"
│ Mode: Calculate Expression
│ Property: PD.GrossAmount
│ Expression: %PROPERTY_{PD.Quantity}% * %PROPERTY_{PD.UnitPrice}%
│
├─ Rule 2: "Discount Amount"
│ Mode: Calculate Expression
│ Property: PD.DiscountAmount
│ Expression: %PROPERTY_{PD.GrossAmount}% * ifNull(%PROPERTY_{PD.DiscountPercent}%, 0) / 100
│
└─ Rule 3: "Net Amount"
Mode: Calculate Expression
Property: PD.NetAmount
Expression: %PROPERTY_{PD.GrossAmount}% - %PROPERTY_{PD.DiscountAmount}%
→ All 3 rules only execute when Quantity, UnitPrice, or DiscountPercent changes
→ The Changed Propertyvalues condition on the group applies to all nested rules
Complete Configuration Examples
These examples show how different calculation modes fit together in a full Class Group configuration. Each rule’s Mode: line states its calculation mode explicitly; where a rule below uses something other than Calculate Expression, that contrast is called out here once rather than after every individual rule.
Example A: Invoice Processing Group
This example uses Calculate Expression, Count Date Or Time, Set Static Values, and Send Email modes:
Class Group:
Name: "Invoice Calculations"
Class: Invoice
Event Handler: BeforeCheckInChangesFinalize
Properties:
┌─ Rule 1: "Calculate Line Total"
│ Mode: Calculate Expression
│ Property: PD.LineTotal
│ Expression: %PROPERTY_{PD.Quantity}% * %PROPERTY_{PD.UnitPrice}%
│ Evaluate as Expression: ✅
│
├─ Rule 2: "Calculate Subtotal"
│ Mode: Calculate Expression
│ Property: PD.Subtotal
│ Expression: Sum(%PROPERTY_{PD.InvoiceLines}.PROPERTY_{PD.LineTotal}%)
│ Evaluate as Expression: ✅
│
├─ Rule 3: "Calculate VAT"
│ Mode: Calculate Expression
│ Property: PD.VATAmount
│ Expression: Round(%PROPERTY_{PD.Subtotal}% * 0.24, 2)
│ Evaluate as Expression: ✅
│
├─ Rule 4: "Calculate Total"
│ Mode: Calculate Expression
│ Property: PD.InvoiceTotal
│ Expression: %PROPERTY_{PD.Subtotal}% + %PROPERTY_{PD.VATAmount}%
│ Evaluate as Expression: ✅
│
├─ Rule 5: "Set Payment Due Date"
│ Mode: Count Date Or Time
│ Property: PD.DueDate
│ Base Date: PD.InvoiceDate
│ Operations:
│ 1. Increase by 30 Days (Fixed)
│ Conditions:
│ - Type: Changed Propertyvalues
│ Value Changed: Propertyvalue Changed
│ Properties: PD.InvoiceDate
│
└─ Rule 6: "Send Payment Reminder"
Mode: Send Email
Allow Email Sending: ✅
To: %PROPERTY_{PD.Customer}.PROPERTY_{PD.Email}%
Subject: Payment Due: Invoice %PROPERTY_{PD.InvoiceNumber}%
Body: <p>Invoice <b>%PROPERTY_{PD.InvoiceNumber}%</b></p>
<p>Total: %PROPERTY_{PD.InvoiceTotal}% EUR</p>
<p>Due: %PROPERTY_{PD.DueDate}%</p>
Conditions:
- Type: Changed Propertyvalues
Value Changed: Propertyvalue Changed
Properties: PD.Status
- Type: Basic Conditions
Property PD.Status = "Sent"
Error Cases:
┌─ "Require Customer"
│ Error Message: "Invoice must have a customer assigned."
│ Block Modification: ✅
│ Conditions:
│ - Type: Basic Conditions
│ Property PD.Customer is empty
│
└─ "Minimum Amount"
Error Message: "Invoice total must be at least 1.00 EUR."
Block Modification: ✅
Conditions:
- Type: Advanced Conditions
Expression: %PROPERTY_{PD.InvoiceTotal}% < 1
Update Related Objects:
└─ "Update Customer Statistics"
Related Object: PD.Customer (Direct)
Update delay (minutes): 0
Conditions:
- Type: Changed Propertyvalues
Value Changed: Propertyvalue Changed
Properties: PD.InvoiceTotal
Example B: Contract Management with Multiple Modes
This example uses Calculate Expression, Period Length, History, Set Static Values, Create Object, and Remove Property modes:
Class Group:
Name: "Contract Management"
Class: Contract
Event Handler: BeforeCheckInChangesFinalize
Properties:
┌─ Rule 1: "Contract Duration (days)"
│ Mode: Period Length
│ Property: PD.DurationDays
│ Start Date: PD.StartDate
│ End Date: PD.EndDate
│ Unit: Day
│ Modifier: 1 (inclusive of both dates)
│
├─ Rule 2: "Expiry Warning"
│ Mode: Calculate Expression
│ Property: PD.ExpiryStatus
│ Expression: let('d', dateDiff(today(), %PROPERTY_{PD.EndDate}%, 'days'),
│ iif(get('d') < 0, 'Expired',
│ iif(get('d') <= 30, 'Expiring Soon', 'Active')))
│ Evaluate as Expression: ✅
│
├─ Rule 3: "Total Amendments Value"
│ Mode: Calculate Expression
│ Property: PD.AmendmentsTotal
│ Expression: Sum(%PROPERTY_{PD.Amendments}.PROPERTY_{PD.AmendmentValue}%)
│ Evaluate as Expression: ✅
│
├─ Rule 4: "Effective Contract Value"
│ Mode: Calculate Expression
│ Property: PD.EffectiveValue
│ Expression: %PROPERTY_{PD.OriginalValue}% + ifNull(%PROPERTY_{PD.AmendmentsTotal}%, 0)
│ Evaluate as Expression: ✅
│
├─ Rule 5: "Preserve Original Submission Date"
│ Mode: History
│ Conditions for Source Version:
│ - Type: Basic Conditions
│ Property "PD.Status" = "Submitted"
│ Property Mappings:
│ - Source: %PROPERTY_{PD.Created}% → Target: PD.OriginalSubmissionDate
│ Throw Exception: ❌
│
├─ Rule 6: "Set Archived Flag"
│ Mode: Set Static Values
│ Property: PD.IsArchived
│ Value: true (Static)
│ Conditions:
│ - Type: Basic Conditions
│ Property "PD.Status" = "Archived"
│
├─ Rule 7: "Remove Rejection Fields When Not Rejected"
│ Mode: Remove Property
│ Property: PD.RejectionReason
│ Conditions:
│ - Type: Basic Conditions
│ Property "PD.Status" NOT equals "Rejected"
│
└─ Rule 8: "Create Renewal Task"
Mode: Create Object
Create as Copy: ❌
Object Type: Task
Create Only If Does Not Exist: ✅
Duplicate Detection:
- PD.SourceContract = (current object)
- PD.TaskType = "Renewal"
Other Property Values:
- PD.TaskName = "Renew: %OBJTITLE%"
- PD.AssignedTo = %PROPERTY_{PD.ContractOwner}%
- PD.SourceContract = (current object)
- PD.TaskType = "Renewal"
Conditions:
- Type: Advanced Conditions
Expression: dateDiff(today(), %PROPERTY_{PD.EndDate}%, 'days') <= 60
Error Cases:
┌─ "Prevent Active Contract Deletion"
│ Error Message: "Active contracts cannot be deleted.
│ Change status to Terminated first."
│ Block Delete: ✅
│ Block Destroy: ✅
│ Conditions:
│ - Type: Basic Conditions
│ Property PD.Status equals "Active"
│
└─ "End Date After Start Date"
Error Message: "Contract end date must be after start date."
Block Modification: ✅
Conditions:
- Type: Compare Properties
Property X: %PROPERTY_{PD.EndDate}%
Comparison: Less Than
Property Y: %PROPERTY_{PD.StartDate}%
Example C: Document Processing with File Operations and Parsing
This example uses Pick Substring, Convert Date, File Operation, Search Objects, Filter Lookup Values, and Calculate Expression modes:
Class Group:
Name: "Document Processing"
Custom Group: ✅
Group Conditions: Object Type = Document
Properties:
┌─ Rule 1: "Parse Document Code from Title"
│ Mode: Pick Substring
│ Pick Substrings From: %PROPERTY_{PD.Title}%
│ Remove Substring from Main String: ❌
│ In Error Case: Do nothing
│ Substrings:
│ ├─ Substring 1:
│ │ Save To: PD.DocumentNumber
│ │ Condition Type: Pick first founded RegExp
│ │ RegExp: [A-Z]{3}-\d{4}-\d+
│ │ → From "Specification DOC-2026-0042 v3" extracts "DOC-2026-0042"
│ │
│ └─ Substring 2:
│ Save To: PD.VersionTag
│ Condition Type: Pick first founded RegExp
│ RegExp: v\d+
│ → Extracts "v3"
│
├─ Rule 2: "Convert Scanned Date to DateTime"
│ Mode: Convert Date
│ Conversion Type: String to Date
│ Value From: PD.ScannedDateText → "15.01.2026"
│ String Format: dd.MM.yyyy
│ Language: fi-FI
│ → Result: 2026-01-15T00:00:00 (DateTime property)
│
├─ Rule 3: "Rename Files with Document Number"
│ Mode: File Operation
│ Operation: PrefixPostfix
│ Prefix: %PROPERTY_{PD.DocumentNumber}% -
│ Postfix: (empty)
│ Always Add: ❌
│ → "Specification.pdf" → "DOC-2026-0042 - Specification.pdf"
│ → Next check-in: unchanged (prefix already exists)
│
├─ Rule 4: "Find Related Specifications"
│ Mode: Search Objects
│ Property: PD.RelatedSpecs (MSLU)
│ Search Value List Items: ❌
│ Property Conditions:
│ - Object Type = Document
│ - PD.ProjectCode equals %PROPERTY_{PD.ProjectCode}%
│ - PD.DocumentType equals "Specification"
│ Keep Previous Content: ❌
│ Max Results: 20
│ → Populates MSLU with all specifications from the same project
│
├─ Rule 5: "Keep Only Active Related Specs"
│ Mode: Filter Lookup Values
│ Property: PD.RelatedSpecs
│ Lookup Values From: PD.RelatedSpecs
│ Conditions:
│ - Type: Basic Conditions
│ Property "PD.Status" equals "Active"
│ → Removes any specification from the list where Status ≠ Active
│
├─ Rule 6: "Detect Document Category by Filename"
│ Mode: Calculate Expression
│ Property: PD.Category
│ Expression: switch(true,
│ like(filename(), '*invoice*'), lookupByName('Invoice'),
│ like(filename(), '*contract*'), lookupByName('Contract'),
│ like(filename(), '*report*'), lookupByName('Report'),
│ lookupByName('Other'))
│ Evaluate as Expression: ✅
│ Conditions:
│ - Type: File Modified
│ File Change Type: File Added
│
└─ Rule 7: "File Summary"
Mode: Calculate Expression
Property: PD.FileSummary
Expression: concat(filecount(), ' file(s): ', filenames(', '))
Evaluate as Expression: ✅
Example D: Order Processing with Value List Resolution and Line Item Creation
This example uses Search Objects (value list mode), Order Lookup Values, Values From MSLU, and Create Object modes:
Class Group:
Name: "Order Processing"
Class: PurchaseOrder
Event Handler: BeforeCheckInChangesFinalize
Properties:
┌─ Rule 1: "Resolve Supplier from External ID"
│ Mode: Search Objects
│ Property: PD.Supplier (SSLU)
│ Search Value List Items: ✅
│ Search By: External ID
│ Search Value: %PROPERTY_{PD.SupplierExtId}%
│ → Text "EXT-42" → resolves to Supplier lookup value with external ID "EXT-42"
│ Conditions:
│ - Type: Changed Propertyvalues
│ Properties: PD.SupplierExtId
│
├─ Rule 2: "Sort Line Items by Amount (Highest First)"
│ Mode: Order Lookup Values
│ Property: PD.OrderLines (MSLU)
│ Lookup Values From: PD.OrderLines
│ Order Type: Numerical
│ Reverse Order: ✅ (descending)
│ Order By: %PROPERTY_{PD.LineAmount}%
│ Amount of Lookups: 0 (keep all)
│ → Reorders the MSLU so highest-value lines appear first
│
├─ Rule 3: "Collect All Product Names"
│ Mode: Values From MSLU
│ Property: PD.ProductSummary (Text)
│ Multi-Select Lookup: PD.OrderLines
│ Conditions for Listed Object: (none)
│ → Result: "Widget A, Widget B, Widget C"
│
└─ Rule 4: "Create Individual Delivery Notes"
Mode: Create Object
Object Type: DeliveryNote
→ Same "confirmed order → create DeliveryNote" setup as the Create Object example
above (Create in Background, Conditions on PD.Status = "Confirmed", etc.) —
only the fields below differ:
Separate Object for Each Value Combination: ✅
Value Combinations:
- From: PD.OrderLines → To: PD.SourceOrderLine
Duplicate Detection:
- PD.SourceOrder = (current object)
- PD.SourceOrderLine = (value from combination)
→ Creates one DeliveryNote per order line when order is confirmed