Skip to content

Investment Events

An investment event records a transaction against a scheme — a purchase, a refinance, or any other change of investment position. The edit form enforces rules as you type (required fields, range checks, currency couplings), and the server enforces a second pass on save (per-role share limits, automatic scheme creation when the event is recorded against a bare address).

Save is only enabled once you’ve changed something. The Save button stays disabled on an unchanged form.

FieldRequired whenWhat you see if missing
Event typeAlways”Event type is required.”
Event statusAlways”Event status is required.”
Ownership typeAlways”Ownership type is required.”

Defaults: a new event opens with Event status = Complete and Ownership type = Whole pre-selected. Both are required, so leaving them at the default counts as filled.

Every other field is optional, but several have conditional requirements — once you fill one field, another becomes required (see Cross-field rules).

The Yield (%) field accepts numbers from 0 to 100 inclusive. Negative yields and yields over 100 are rejected.

Where this lives: src/services/web/src/lib/validation/investment-event.ts.

The Units field rejects fractional values. Use a whole-number count of investment units (e.g. 100, not 100.5).

Where this lives: src/services/web/src/lib/validation/investment-event.ts.

WALT is a non-negative whole number of years

Section titled “WALT is a non-negative whole number of years”

The WALT (years) field is the Weighted Average Lease Term of the deal, in whole years. It’s always optional — not every deal has a calculated WALT (vacant possessions, ground rents, etc.) — but if you enter a value it must be a non-negative integer. Fractional years are rejected with “WALT must be a whole number.” The unit is fixed; there is no associated unit picker. Stored as INT to match the shape of OccupierEvent.LeaseLength.

Where this lives: src/services/web/src/lib/validation/investment-event.ts.

The three size fields — Size (local), and the auto-computed Net and Gross — all reject negative numbers. Zero is allowed; negative is not.

Description has a practical 75-character limit

Section titled “Description has a practical 75-character limit”

The Description textarea limits you to 75 characters as you type. (The server accepts a little more if a record arrives via the API, but the form caps what you can enter.)

When the event isn’t a Whole ownership transaction, the Investment Percentage (%) field appears. It accepts 0-100 inclusive.

If you enter a yield, the Yield size unit dropdown becomes required. (“Size unit is required when yield is specified.”) Leave the yield blank to leave the unit blank.

The picked yield unit (Gross / Net / Initial / Cap Rate — the UnitName of the chosen SizeUnit) surfaces on the Investment Events list as a separate Yield Type column next to the Yield (%) value, and is included in Excel exports. The column reads from a denormalised YieldSizeUnit field on the list view, so the underlying join doesn’t run at read time.

If you enter a purchase price, the Currency dropdown becomes required. (“Currency is required when purchase price is specified.”)

Closed date appears when “Is Closed” is ticked

Section titled “Closed date appears when “Is Closed” is ticked”

Ticking Is Closed? reveals a Closed Date field below it. Unticking hides the field again; any date entered is kept on the record but won’t display until you re-tick the box.

”Replaces Owner” auto-retires the prior event

Section titled “”Replaces Owner” auto-retires the prior event”

Picking a prior investment event in the Replaces Owner field tells the system that this new event supersedes that one. On save, the prior event is automatically ticked Is Closed? with its Closed Date set to the new event’s date (the day ownership transferred). No manual edit on the prior event is required.

The cascade is one-way: clearing Replaces Owner on the new event later does not un-retire the prior event. To re-open the prior event, edit it manually.

Confidential events redact price and yield in Excel exports

Section titled “Confidential events redact price and yield in Excel exports”

Ticking Is Confidential? marks the deal as commercially sensitive. In the app itself, everything still displays as normal — the field exists only to gate the Excel export.

When a confidential event is included in an Excel export of the Investment Events list, the following cells are written as blank instead of their numeric value:

  • Yield (%) — PercentageYield
  • Purchase Price — local, EUR, GBP, USD (PurchasePriceLocal, PurchasePriceEUR, PurchasePriceGBP, PurchasePriceUSD)

A blank cell (rather than a placeholder like “Confidential”) keeps the column numeric — Excel formulas such as SUM and AVERAGE over the column continue to work, simply skipping the redacted rows. Every other column (description, scheme, address, size, dates, parties, etc.) exports as usual. The row is not removed from the export; only the four price columns and the yield column are redacted.

A Confidential badge appears on the event’s card in the detail view so the flag is visible at a glance. The list page has an optional Is Confidential column (off by default — turn it on from the column picker) so you can filter to find them.

Where this lives: backend src/services/api/app/app.api/Services/Export/ExcelExportService.cs (the ConfidentialRedactedFields set), badge at src/services/web/src/lib/components/badges/BadgeConfidential.svelte.

Investment percentage appears when ownership isn’t Whole

Section titled “Investment percentage appears when ownership isn’t Whole”

If Ownership type is set to anything other than Whole (for example, Share or Joint), the Investment Percentage (%) field slides into view. For Whole ownership the field is hidden and the percentage isn’t recorded.

Size and unit auto-populate from the scheme

Section titled “Size and unit auto-populate from the scheme”

When you create a new investment event and link it to a single scheme, the form tries to seed Size and Size Unit from that scheme. It walks the scheme to its first development, then the first use of that development, then picks up the size unit recorded there. The scheme’s stored size (kept in canonical square metres) is converted to that unit. Net or Gross is chosen based on what the unit represents.

The auto-fill runs once per scheme during the create flow. If you delete the seeded value, it stays empty — switching schemes resets the rule and a new seed runs.

If the scheme can’t be resolved (no developments, no uses, no size unit on the first use), the form leaves Size and Unit blank rather than guessing.

When you add or remove an Investor company, every investor’s Share % is reset to an equal split. Three investors get 33% each (give or take rounding); add a fourth and all four jump to 25%.

The auto-distribute runs only when the count changes. If you enter custom shares after adding the investors, they stay until the count changes again.

When an investment event is linked to more than one scheme, each scheme picks up its own Share % input next to its name in the Schemes list. The shares auto-distribute equally when the count changes — two schemes get 50% each, three schemes get 33.33% each — and stay put once you’ve edited them, just like the investor rule.

A single-scheme event has no share input; the underlying PercentageShare stays null in the DB. Dropping back to one scheme clears any previously-set share automatically.

Individual shares are bounded 0–100. The total across all linked schemes can’t exceed 100% — the form shows a running total under the list and turns it red with “Total share is X% — cannot exceed 100%” when over-allocated. The server re-checks the rule on save and rejects with the same message. Partial allocations are still allowed (sum below 100% is fine).

The Share % column on the Investor list appears as soon as there’s at least one investor. A single investor can hold a partial share (for example, one investor at 75% with the remainder unallocated), so the field isn’t gated on having two or more parties.

The same field doesn’t appear on the Vendor role — shares are tracked on the buying side only.

Unknown placeholder on Investor and Vendor roles

Section titled “Unknown placeholder on Investor and Vendor roles”

For both Investor and Vendor, an Unknown checkbox sits next to the company picker. Ticking it records that there is a party in this role whose identity isn’t known, rather than that the role is empty. For Investor, the unknown placeholder also takes a percentage share (rounds to the nearest integer); for Vendor there’s no share field.

See also: Unknown and optional fields.

Replaces Owner only appears when there’s something to replace

Section titled “Replaces Owner only appears when there’s something to replace”

The Replaces Owner dropdown only shows up when the scheme already has at least one other investment event. The list excludes the event you’re currently editing.

Validation messages under the Investor list

Section titled “Validation messages under the Investor list”

While editing, the form watches the total of all investor shares and shows one of two messages just below the Investor list:

  • If the total exceeds 100%: a red “Total share is N% — cannot exceed 100%”.
  • If the ownership type is anything other than Share and the total is greater than 0% but not exactly 100%: an amber “Total share is N% — must equal 100% for {Whole|Joint} ownership”.

Under Share ownership the form is happy with any total from 0% up to 100% (typical case: one share-investor at 75% with the remaining 25% unallocated). Under Whole ownership the parties on file should add up to 100%.

The server re-runs the required-field checks. Event type, status, and ownership type must be present.

The server caps the total percentage share within each role at 100%. If your investor shares add up to more than 100% you get “Total percentage share per role cannot exceed 100%.”

You can mark a role as Unknown only once per event. Two Unknown Investor entries on the same event are rejected with “Only one unknown company is allowed per role.”

If the event is being created against an address that doesn’t have a scheme yet, and the request supplies a building type and at least one size value, the server creates an unverified placeholder scheme + development + use on the spot, then attaches the event to it. The placeholder takes the address’s first line as its name (trimmed to 255 characters) and picks the first building use mapped to the building type. The new scheme is marked Unverified so it shows up in any default-filtered list for review.

This only fires on create, only when no scheme is linked, and only when there’s enough size data to justify making the scheme. Editing an existing event never triggers it.

The save carries the RowVersion of the record you loaded. If another user has updated the same event since, the save fails with a concurrency error (HTTP 412). See Concurrent edits.

  • An InvestmentEventCreated or InvestmentEventUpdated event fires, which downstream services pick up to:
    • Synchronise scheme ownership when the event replaces a previous one (the chain of “purchased from”, “sold to” relationships gets updated).
    • Recalculate the completeness score for the event (eventually, via the nightly job). See Completeness score.
  • The event’s row in the InvestmentEventList query view is upserted so list pages reflect the change on the next refresh.
  • An audit log entry records who saved what.
  • Schemes — auto-created schemes from this page show up as unverified placeholders.
  • Companies — the investor and vendor roles draw from the company directory.
  • Portfolios — Share % behaviour on portfolio parties follows a related but separate rebalance pattern.
  • Unknown and optional fields — semantics of the Unknown checkbox.
  • Units and display — net vs gross, sqm vs sqft, how Size is presented.
  • Currency — how Purchase Price is converted to your preferred display currency.
  • Concurrent edits — what happens when two users edit the same event.
  • Frontend validation: src/services/web/src/lib/validation/investment-event.ts
  • Edit form: src/services/web/src/lib/components/panels/InvestmentEventEdit.svelte
  • Model: src/common/models/Models/InvestmentEvent.cs
  • Command handlers: src/services/api/app/app.api/Handlers/Commands/InvestmentEvents/
  • Scheme-ownership sync: src/services/api/app/app.api/Services/Model/SchemeOwnershipService.cs
  • List-view mapper: src/common/models/Services/QueryViews/InvestmentEventListViewMapper.cs