Skip to content

Serving multiple companies from one project

A common need for groups of companies: one Recognito project handling invoices for several subsidiaries without per-subsidiary project duplication. The pattern is discrimination by a tag column. One entity hosts data for every subsidiary; a discriminator column narrows lookups to the right subsidiary.

This is the most-mature use of the rule engine. Read the simpler recipes first if you haven't.

Teaches: composite-key matching with pure AND, the discriminator pattern, multi-tenancy without per-tenant duplication.

The scenario

The Acme group has four subsidiaries:

  • Acme USA
  • Acme UK
  • Acme Germany
  • Acme Lithuania

Each has its own vendor master, GL chart, approver list, and ERP integration. The CFO wants one Recognito project handling AP for all of them, because:

  • Separate projects per subsidiary mean separate configurations to maintain.
  • Adding a fifth subsidiary should be cheap, not a project-creation exercise.
  • The reviewers handle invoices from any subsidiary; they shouldn't need to switch projects.

The catch: every lookup needs to be narrowed to the right subsidiary. An Acme USA invoice shouldn't pull up Acme Germany's vendors.

Step 1 — Pick a discriminator

The discriminator is the column that tags each entity row with its subsidiary. Common values:

  • A short code: US, UK, DE, LT.
  • The subsidiary's customer ID in the parent company's master: 100, 200, 300, 400.
  • The subsidiary's legal-entity name.

For this recipe, use 2-letter codes: customerInitials is the discriminator column, values US, UK, DE, LT.

Step 2 — Set up the Customer Initials entity

This entity maps an extracted identifier from the document to the customerInitials code.

Entity name: Customer Initials

Columns:

  • CustomerId — the value the engine extracts from the document (e.g., the customer's company registration number).
  • customerInitials — the short code.

Rows:

CustomerIdcustomerInitials
100-USA-CORPUS
200-UK-LTDUK
300-DE-GMBHDE
400-LT-UABLT

The exact CustomerId values are whatever appears on incoming documents. The mapping function is "translate the document's identifier into our internal short code."

Step 3 — Create the discriminator definition

Inside Customer Initials, create a Mapping Definition:

Definition Name:   Initialize Customer Initials
Type:              Mapping Definition
Target entity:     Customer Initials

Context columns:
  1. CustomerId       Matching Type: Exact

Mapped columns:
  - customerInitials (metadataFields)   Transform: No Transform

When a document arrives, this definition reads the extracted CustomerId and writes the matching customerInitials code to a Metadata field.

Step 4 — Set up shared entities with the discriminator column

Every entity that holds subsidiary-specific data needs a customerInitials column.

Vendors

customerInitialsvendorIdvendorNamevendorAccountcurrency
USV-100Acme USA Supplier A10-USA-001USD
USV-101Acme USA Supplier B10-USA-002USD
UKV-200Acme UK Supplier A10-UK-001GBP
UKV-201Acme UK Supplier B10-UK-002GBP
DEV-300Acme DE Supplier A10-DE-001EUR
LTV-400Acme LT Supplier A10-LT-001EUR

All four subsidiaries' vendors are in one Vendors entity, distinguished by customerInitials.

GL Accounts, Approver Routing, Departments

Same pattern for every entity that holds subsidiary-specific reference data. Each entity gets a customerInitials column; rows mix across subsidiaries.

Step 5 — Update every definition's Context

The first Context column on every definition becomes customerInitials. The remaining Context columns are the previous narrowing logic.

For the Vendor lookup definition:

Definition Name:   Initialize Vendors
Type:              Mapping Definition
Target entity:     Vendors

Context columns:
  1. customerInitials   Matching Type: Exact
  2. vendorId           Matching Type: Exact

Mapped columns:
  - vendorName (metadataFields)     Transform: No Transform
  - vendorAccount (metadataFields)  Transform: No Transform
  - currency (metadataFields)       Transform: No Transform

The composite AND (customerInitials=US, vendorId=V-100) matches one specific vendor row. The lookup narrows correctly.

Do the same for every other definition. Each one gets customerInitials as its first Context column.

Step 6 — Update the Initial Workflow

Initial_Workflow's first step needs to be the discriminator-setting one:

#StepPurpose
1Initialize Customer InitialsSets customerInitials from extracted CustomerId
2Initialize VendorsVendor lookup narrowed by discriminator
3GL LookupGL narrowed by discriminator
4Approver RoutingApprover routing narrowed by discriminator
5+All other steps

The discriminator must be set first. The sequential feed-through means subsequent steps see the freshly-written customerInitials value and use it in their Context lookups.

If you put step 1 anywhere else, all the downstream steps run with customerInitials=(empty) and silently no-op.

Step 7 — Test

Upload a sample Acme USA invoice. The extraction produces:

  • CustomerId = 100-USA-CORP
  • vendorId = V-101

Initial_Workflow runs:

  • Step 1: customerInitials = US
  • Step 2: matches Vendors row (US, V-101) → writes Acme USA Supplier B, 10-USA-002, USD.
  • Subsequent steps narrow by US and produce US-specific results.

Upload an Acme Germany invoice with CustomerId=300-DE-GMBH. The cascade narrows everything to DE. Same workflow, different subsidiary, different data.

Adding a new subsidiary

When Acme acquires a fifth company (say Acme France, FR):

  1. Add a row to Customer Initials: (CustomerId=500-FR-SAS, customerInitials=FR).
  2. Add Vendors entity rows: all of Acme France's vendors, with customerInitials=FR.
  3. Add GL Accounts entity rows: Acme France's GL chart, with customerInitials=FR.
  4. Add Approver Routing entity rows: Acme France's approvers, with customerInitials=FR.
  5. Done. No new definitions, no new workflows.

The discriminator pattern absorbs the new subsidiary into the existing setup.

Trade-offs

The pattern is powerful but has cost:

  • Every Context grows by one column. Lookups have to AND against the discriminator first. A bit more work per lookup; not noticeable in practice.
  • Entities mix data across subsidiaries. Privacy-sensitive subsidiaries may resist seeing each other's data in the same table. If this matters, the alternative is separate projects (with the duplication cost).
  • The discriminator definition must run first. Forgetting this breaks every downstream step. Strong convention required.
  • Reviewers see invoices from any subsidiary. Permission scoping is via Personal Visibility (the reviewer sees only their assigned documents), but the entity data leaks across subsidiaries via Custom Filter dropdowns (see Users & permissions).

For most multi-subsidiary cases, the trade-offs are worth it. For ultra-sensitive separation, separate projects remain the right call.

What this teaches

  • One project can serve N subsidiaries with a discriminator column.
  • The discriminator must be the first Context column on every definition.
  • Initialize Customer Initials (or its equivalent) must run first in Initial_Workflow.
  • Adding subsidiaries is incremental — rows, not configuration. This is what makes the pattern scale.

The pattern generalizes to any multi-tenant scenario: per-customer pricing tables, per-region tax rules, per-department workflows. The shape is the same — add the discriminator column, narrow every Context.

What's next