Aller au contenu

Entity Model — Shared Foundation

Status: Draft v1 (2026-04-12) Audience: Finance, Cap Table, Legal/HR, Engineering Scope: Canonical entity taxonomy, ownership structure, consolidation methodology, and proposed data model for the Groupe Suffren multi-entity reporting platform inside Aletheia.

This document is the single source of truth for how the group's legal entities, ownership, and consolidation scopes are represented in Aletheia. Two roadmaps build on it:

  • docs/finance-roadmap.md — dashboards, consolidation, cost control, cash flow, revenue analytics
  • docs/captable-roadmap.md (to be written by the cap table roadmap session) — individual holder tracking, PV d'AG / ODM generation, shareholder movement workflows

Neither roadmap may redefine the concepts in this document. Changes here propagate to both; downstream roadmaps cite section numbers and refuse to diverge.


1. Share class vocabulary

The group uses several instrument types. Each must be represented in the data model as its own share_class row, not as a string or enum — we expect to add custom classes in the future (e.g., new regulatory regimes, new foreign jurisdictions).

Code Name Nature today Capital/Voting Rights (CVR) Financial Rights (FR) Notes
OS Ordinary Shares Equity Yes (pro rata) Yes (pro rata) The default instrument. CVR and FR move together.
CSa Class A shares Equity Yes Reduced / split Used in FR SELAS structures to cap practicing-dentist control.
CSb Class B shares Equity Reduced / none Enhanced Pairs with CSa to move financial rights to the group.
CSc Class C shares Equity Variable Variable Minority-holder class (e.g. practitioner at THS holding 20% FR).
CL Convertible Loan Debt today None 100% economic FR of borrower via "prêt participatif" Interest flows; tax deductible at borrower. Converts to equity on trigger.
CB Convertible Bond Debt today None 100% economic FR of borrower via "prêt participatif" Same concept as CL, different legal form. Fiscal integration preserved.

CVR vs FR — two independent cascades

This is the most important modelling rule in the document. Capital/Voting Rights (who controls the entity legally) and Financial Rights (who is entitled to the cash: dividends, interest, liquidation proceeds) do not move together in this group's structure.

  • CVR cascade answers: "Who votes? Who can legally block a resolution?"
  • FR cascade answers: "Whose bank account does the cash end up in?"

They diverge substantially. Example: SDT holds 49% CVR in operating practices but ~100% FR in them. A report that collapses these into a single "% ownership" column will produce wrong numbers.

Any report computing ownership must specify which dimension it means. The UI must show the dimension explicitly; the data model must expose both.

Practicing-dentist pattern

French law requires that dentists practicing at a clinic hold at least some shares. Every operating practice (THS, CDA, PDS, VSM) has a bucket of practicing dentists holding ~1% OS cumulatively (typically 1 share each). Legal/HR manages this with frequent small movements (new hires → issue 1 share; exits → buy back 1 share).

This pattern is consistent enough to be a first-class rule, not an exception. The data model treats "practicing dentist" as a holder category, and the cap table roadmap will automate the standard 6-document onboarding / 3-document exit packs off it.


Reproduced verbatim from the source of truth in temp/pennylane_reusable/ROADMAP_PROMPT.md. Do not rewrite this in downstream documents; link back here.

ASCII tree

CHC ─────── FR top holding (new — governance + new FR shareholders)
 │           Also acts as service co on occasion (dual-role)
 │           Pennylane: soon
 │  100% OS
SUI ─────── LU intermediate holding (former top co)
 │           Pennylane: no — Excel GL import
 │  CL → 100% FR of AST  (economic, via prêt participatif today)
AST ─────── BE central
 │           Pennylane: no — Excel GL import
 │           CVR: 100% partner dentists
 │           FR : 100% CHC — indirect today via SUI CL;
 │                 future: also direct CHC→AST CL pro rata for new clinic acquisitions
 │  CSa + CSb → 49% CVR / 100% FR
SDT ─────── BE central
 │           Pennylane: no — Excel GL import
 │           51% CVR : partner dentists
 ├── 100% OS ──────► SDV  (FR service company)  — 100% CVR / 100% FR
 ├── CSa + CSb ────► THS  (FR dental practice)  — 49% CVR / 80% FR
 │                          20% FR via CSc → practicing dentist (to be acquired by group)
 │                          51% CVR → practicing dentists
 ├── CSa + CSb ────► CDA  (FR dental practice)  — 49% CVR / 99% FR
 │                          51% CVR + ~1% OS → practicing dentists
 ├── CSa + CSb ────► PDSh (FR SPFPL)             — 49% CVR / 84% FR
 │                   │     16% FR via CSc → practicing dentist (to be acquired by group)
 │                   │     51% CVR → practicing dentists
 │                   │
 │                   └── 99% OS ──► PDS (FR dental practice)
 │                                   ~1% OS → practicing dentists at PDS
 └── CB ───────────► VSMh (FR SPFPL)             — 0% CVR / 100% FR (economic)
                     │     100% CVR (OS) → practicing dentist
                     │     [CB today = debt; fiscal integration VSM-VSMh; interest tax deductible]
                     │     [On conversion → equity / dividends; loses interest deductibility]
                     └── 99% OS ──► VSM (FR dental practice)
                                     ~1% OS → practicing dentists at VSM

Future flow (not yet in place):
  CHC ──── direct CL ────► AST    (pro rata, for new clinic acquisitions;
                                    combined with indirect SUI CL, CHC still ends
                                    at 100% FR of AST)

Canonical entity table

Code Country Type Pennylane Direct parent Parent CVR % Parent FR % Instrument(s) Other stakeholders Consolidation
CHC FR Top holding (+ occasional service co) Soon — (ultimate parent) N/A
SUI LU Intermediate holding No (Excel) CHC 100% 100% OS Full
AST BE Central holding No (Excel) CHC (via SUI CL today; future direct CL + indirect) 0% 100% (economic, via CL) CL / prêt participatif 100% CVR partner dentists Full
SDT BE Central holding No (Excel) AST 49% 100% CSa + CSb 51% CVR partner dentists Full
SDV FR Service company Yes SDT 100% 100% OS Full
THS FR Dental practice Yes SDT 49% 80% CSa + CSb 51% CVR practicing dentists; 20% FR via CSc (to acquire) Full
CDA FR Dental practice Yes SDT 49% 99% CSa + CSb 51% CVR + ~1% OS practicing dentists Full
PDSh FR SPFPL holding Yes SDT 49% 84% CSa + CSb 51% CVR practicing dentists; 16% FR via CSc (to acquire) Full
PDS FR Dental practice Yes PDSh 99% 99% OS ~1% practicing dentists at PDS Full (via PDSh)
VSMh FR SPFPL holding Yes SDT 0% 100% (economic, via CB) CB / prêt participatif 100% CVR practicing dentist (via OS) Full
VSM FR Dental practice Yes VSMh 99% 99% OS ~1% practicing dentists at VSM Full (via VSMh)

Total as of 2026-04: 11 legal entities. Expected to grow as new clinic acquisitions bring new SPFPL+operating-practice pairs under AST via the direct-CL flow.


3. Consolidation methodology

Scope and method

  • All 11 entities are full-consolidated, including the 49%-CVR operating practices. Full consolidation is justified by shareholder agreements that give the group substantive control (board appointment rights, veto rights, reserved matters) despite the voting minority. This is the standard French dental-group structure — the "non-practicing holder majority CVR / operator majority FR" pattern is specific to the regulatory constraint that only practicing dentists may hold voting control of a clinic.
  • No equity method, no proportional consolidation. The data model does not need to support them today, but should not preclude them (a consolidation_method column on the entity row keeps the door open).

Minority interests

  • Minority interests are computed from the FR side, not CVR.
  • Partner dentists' CVR majority (e.g., the 51% voting at THS) does not dilute the group's consolidated net income. Only their FR share does.
  • Example: for CDA, minority interest on net income = 1% of CDA net income (the ~1% OS held by practicing dentists), not 51%.
  • For THS, minority interest = 20% (the CSc bucket held by a practitioner the group intends to acquire), not 51%.

The consolidation engine computes minority interest as (1 - group_FR_pct) × entity_net_income, walking up the FR cascade.

Practicing-dentist bucket

  • Every operating practice has a ~1% OS bucket for practicing dentists. Legal/HR maintains the exact headcount and share count via the onboarding / exit document packs.
  • Individual holders are populated from day one — both roadmaps ship Phase 1 together. The cap table workstream populates each practicing dentist as an individual holders row; finance views aggregate them per entity at query time (showing "practicing dentists ~1%" as a grouped line in P&L minority-interest calculations).
  • The bucket is always modelled as a minority interest at the operating practice level — even though FR is only ~1%, it should roll up through the FR cascade like any other minority.

CL / CB instruments

This is the trickiest part of the model. The consolidation engine must get this right.

  • Convertible Loans (CL) and Convertible Bonds (CB) here have a "prêt participatif" clause that captures 100% of the economic rights (cash flows) of the underlying entity.
  • Today, legally, they are debt. On the borrower's books (AST, VSMh): interest expense flowing up. On the lender's books (SUI, SDT): interest income.
  • This arrangement preserves two benefits that would be lost on conversion:
  • Fiscal integration (intégration fiscale) — e.g., VSMh-VSM form an integrated tax group in France
  • Interest deductibility — interest on the CL/CB is tax-deductible at the paying entity, whereas dividends post-conversion would not be

Consolidation rule: attribute 100% FR to the lender regardless of legal form.

A naive shareholder cascade (% OS × % OS × ...) will produce 0% FR for the group at AST and VSMh, because the group holds no equity there. That is wrong by the economics. The model must encode an explicit "economic FR claim via debt instrument" on the ownership_links row, independent of the equity percentage.

  • Interest cash flows today and dividend cash flows post-conversion must both route to the same economic owner (the CL/CB lender).
  • Post-conversion, the same ownership_links row flips from convertible_debt to equity and the economics stay consistent (but tax treatment changes — a fiscal integration group may break; see §4 fiscal_integration_groups).

Conversion events

The data model supports conversion as a time-versioned instrument change on an existing ownership_links row, not as a delete-and-recreate. This is important for historical reports: a P&L for FY25 with VSMh's CB still in place must continue to show interest expense at VSMh even after the conversion happens in, say, FY27.

ownership_links gains an effective_from / effective_to pair. Conversion is implemented as:

  1. Close the existing row at the conversion date (effective_to = <date>)
  2. Open a new row for the same parent/child with instrument_type='OS', effective_from = <date>, matching CVR/FR percentages
  3. Log an ownership_events row with event_type='conversion', referencing both the old and new links

The UI should preview the consolidated impact (P&L: interest → 0, dividends → X; BS: debt → 0, equity → Y; tax: integration group member change) before executing the event.


4. Data model proposal

Proposed Django tables. Names are suggestions; the finance roadmap Phase 1 ticket will finalize them. All tables live in a new apps/entities/ module (not mixed into apps/practices/).

entities

One row per legal entity in the group (including future entities).

Column Type Notes
id BigAutoField
code CharField(10) unique "CHC", "SUI", "AST", "SDT", "SDV", "THS", "CDA", "PDS", "PDSh", "VSM", "VSMh"
name CharField(200) Full legal name ("Suffren Dental Trust SRL")
country CharField(2) ISO alpha-2 ("FR", "BE", "LU")
entity_type CharField(choices) top_holding, intermediate_holding, spfpl_holding, central_holding, operating_practice, service_company
legal_form CharField(50) SELAS, SELARL, SPFPL, BV, SARL, SA...
siren CharField(9) blank For FR entities
siret CharField(14) blank For FR entities (headquarters SIRET)
vat_number CharField(20) blank For cross-border invoicing
fiscal_year_end_month PositiveSmallInteger Default 9 (Sept). Per-entity because legal FY may differ.
fiscal_year_end_day PositiveSmallInteger Default 30.
pennylane_status CharField(choices) not_applicable, pending, active — drives which ingestion path is used
pennylane_company_id Integer null Links to Pennylane companies.id when active
consolidation_method CharField(choices) full, equity, proportional (only full used today; future-proof)
lifecycle_status CharField(choices) active, in_creation, dormant, dissolved
created_on DateField Legal creation date
dissolved_on DateField null Populated when lifecycle_status=dissolved
acquisition_date Date null Date the entity was first consolidated into the group. Null for founding entities.
acquisition_fy CharField(4) null Fiscal year of acquisition (e.g., "FY26"). Derived from acquisition_date. Used for vintage grouping and new/existing classification.
consolidation_start_date Date null Date from which financials are included in consolidated reports. May differ from acquisition_date (e.g., stub period).
notes TextField blank
+ AuditModel fields Inherits created_at, updated_at, created_by, updated_by

Seed data: one row per entity in §2's canonical table. CHC starts with pennylane_status='pending'; SUI/AST/SDT start with pennylane_status='not_applicable'; FR operating entities start with pennylane_status='active'.

Share class and register tables — cap table domain

The share class registry (share_classes), per-entity class rights (share_register / registre des titres), and convertible instrument tracking (bond_register / registre des obligataires) are owned by the cap table roadmap. Their schemas are defined there, not here. The shared foundation defines only the derived aggregate that finance reads: ownership_links below.

The derived/cached finance view. One row per parent→child entity-to-entity ownership relationship, time-versioned. Both CVR and FR percentages are stored; they are independent.

cvr_pct and fr_pct are computed/cached fields, not independently maintained: - For equity instruments (OS, CSa, CSb, CSc): derived from the share register — (shares_held / shares_outstanding) × class_rights_weight, summed across all classes the parent holds at the child entity. - For convertible debt instruments (CL, CB): stated from the bond register's contractual_fr_pct / contractual_cvr_pct. - Recomputed on any cap table event affecting the child entity (share issuance, transfer, buyback, conversion, class rights amendment, new bond issuance, bond conversion) via a service recompute_ownership_percentages(entity). - A nightly reconciliation job verifies cached values match computed values and alerts on drift. - Finance reads these cached values directly — it never queries the share or bond registers.

Column Type Notes
id BigAutoField
parent_entity FK entities The owner (another group entity)
child_entity FK entities The owned
instrument_nature CharField(choices) equity or convertible_debt — replaces the old share_class FK. Finance needs to know debt vs equity for elimination rules and conversion tracking; the specific class breakdown is cap table domain.
cvr_pct Decimal(7,4) 0.0000–100.0000. Computed/cached — see derivation model above.
fr_pct Decimal(7,4) 0.0000–100.0000. Computed/cached — see derivation model above.
economic_claim_pct Decimal(7,4) null For convertible_debt: the "prêt participatif" economic share of the borrower (e.g., 100%). Lets FR reports differentiate "FR via equity" from "FR via debt instrument".
effective_from Date
effective_to Date null Null = currently in force
source_event FK ownership_events null Points at the event that created this link
notes TextField blank
+ AuditModel fields

Seed data:

Parent Child Nature CVR % FR % Notes
CHC SUI equity 100 100
SUI AST convertible_debt 0 0 economic_claim_pct=100 (100% FR via prêt participatif)
AST SDT equity 49 100
SDT SDV equity 100 100
SDT THS equity 49 80
SDT CDA equity 49 99
SDT PDSh equity 49 84
SDT VSMh convertible_debt 0 0 economic_claim_pct=100 (100% FR via prêt participatif)
PDSh PDS equity 99 99
VSMh VSM equity 99 99

Individual holders (practicing dentists, partner dentists) are populated from day one via the holders table — see below. Finance views aggregate them per entity to show "practicing dentists ~1%" as a grouped line.

holders

Stores individual holder data. Populated by the cap table workstream from day one — both roadmaps ship Phase 1 together, so individual holders are available from the start.

Column Type Notes
id BigAutoField
holder_type CharField(choices) entity (internal group entity), individual (natural person)
entity FK entities null Set when holder_type=entity
individual_person ForeignKey or TextField For individual holders (dentists, partners). Cap table roadmap defines the detail.
notes TextField blank
+ AuditModel fields

Individual holders own shares and bonds tracked in the cap table registers (share register, bond register). The entity-to-entity ownership_links table is the derived aggregate — its CVR/FR percentages are recomputed from the underlying individual-level data. Finance reads ownership_links only; the consolidation engine sums all individual holder FR% per entity at query time to compute aggregate minority percentages (e.g., "practicing dentists at VSM ~1%").

ownership_events

Time-versioned audit trail of everything that changes ownership. Drives the cap table roadmap's document automation and the finance roadmap's historical-report consistency.

Column Type Notes
id BigAutoField
event_type CharField(choices) issuance, transfer, buyback, conversion, csc_acquisition, capital_increase, capital_reduction, dissolution
entity FK entities The entity whose cap structure changed
event_date Date
description TextField
affected_links M2M ownership_links Links closed or opened by this event
pv_document FileField blank Optional — signed PV d'AG / DUA
odm_document FileField blank Optional — signed ODM
+ AuditModel fields created_by is who recorded the event

Rule: ownership_links rows are never updated in place. All changes go through an ownership_events row that opens new links and closes old ones. This makes every historical report reproducible.

aggregation_scopes

Separate from ownership. Models the management reporting tree in §5. Scope membership is computed from entity attributes (country, entity_type, acquisition_date, SPFPL→practice relationship) via rules — not from manually-maintained membership tables. Adding a new entity with the right attributes automatically places it in the correct scopes.

Column Type Notes
id BigAutoField
code CharField(50) unique e.g., PDS_GROUP, FR_PRACTICES, GROUP_EXCL_BE, VINTAGE_FY26
name CharField(100) Display name
scope_type CharField(choices) practice_group, subtotal, group_total, vintage
parent_scope FK aggregation_scopes null For tree structure
membership_rule TextField blank Human-readable rule (e.g., "all FR operating practices where acquisition_fy < current_fy"). Computed membership engine reads this or equivalent programmatic config.
+ standard fields

intercompany_elimination_rules

Declarative elimination rules. The consolidation engine reads this table; no rule is hardcoded in Python.

Column Type Notes
id BigAutoField
code CharField(30) unique e.g., SDV_PRACTICE_SERVICES, SUI_AST_LOAN_INTEREST
description TextField
from_entity FK entities The "booking" side of the intercompany pair
to_entity FK entities The counterparty
p_and_l_lines JSONField List of report lines to eliminate in pairs (e.g. ["Other Incomes", "Legal and Professional"] for a service-co invoice)
balance_sheet_lines JSONField Typically receivable/payable pair (["Trade receivables", "Trade payables"])
effective_from / effective_to Date Rule may be seasonal / start at a future date
fires_at_scope CharField(50) Scope label at which this rule fires (e.g., "Group excl. BE", "PDS practice group"). Rule fires at this scope and any scope that contains it.
notes TextField

Seed rules: - SDV_PRACTICE_SERVICES — SDV revenue ↔ practice cost of service, fires at "Group excl. BE" - SUI_AST_CL_INTEREST — SUI financial income ↔ AST financial expense on the CL, fires at "Belgium entities total" - SDT_VSMh_CB_INTEREST — SDT financial income ↔ VSMh financial expense on the CB, fires at "FR practices total" - AST_SDT_SHARE_CAPITAL — AST investments in SDT ↔ SDT share capital, fires at "Belgium entities total" - Future: CHC_SERVICE_CO_FEES when CHC starts invoicing in its service-co role

fiscal_integration_groups + fiscal_integration_memberships

Tracks French fiscal integration groups (intégration fiscale). This is a declared fact, not mechanically derivable from ownership or instruments — it's a fiscal election with technical conditions that can change independently. The system records the fact; it does NOT try to auto-compute whether integration holds.

fiscal_integration_groups

Column Type Notes
id BigAutoField
name CharField(100) e.g., "VSMh-VSM integration"
notes TextField blank
+ AuditModel fields

fiscal_integration_memberships

Column Type Notes
id BigAutoField
group FK fiscal_integration_groups
entity FK entities
effective_from Date
effective_to Date null
+ AuditModel fields

Seed data: one group "VSMh-VSM" with two memberships (VSMh, VSM), effective_from = group creation date. Finance team maintains this manually. ~2-3 groups today, growing with acquisitions. The conversion preview feature warns "this entity is in a fiscal integration group — converting the CB may break it."

period_close

Per-entity monthly close status. Drives the "as-reported" vs "current truth" dual-view pattern (see §4a).

Column Type Notes
id BigAutoField
entity FK entities
period CharField(7) YYYY-MM format
status CharField(choices) open, in_close, closed
closed_at DateTimeField null When status moved to closed
closed_by FK User null Who closed it
reopened_at DateTimeField null If re-opened
reopened_by FK User null
reopen_reason TextField blank Audit trail for re-opens
+ AuditModel fields

Unique constraint on (entity, period). Closed months are immutable unless explicitly re-opened (requires CFO-role approval). Re-opening creates an audit trail entry.

Historical report guarantee

Every report query must take a as_of_date parameter and filter ownership_links / aggregation_scope_members / intercompany_elimination_rules by effective_from <= as_of_date AND (effective_to IS NULL OR effective_to > as_of_date). This is how a FY25 report stays correct in FY27 even if the ownership structure has changed.

Convention: if a report asks for "the state at the close of period P", as_of_date = last day of P. If a report is live/ongoing, as_of_date = today.

4a. Monthly close workflow and dual-view reporting

The finance team operates a monthly close calendar: - ~6th of month+1: first GL extract pulled (preliminary) - 6th–15th: chase missing invoices, correct entries, accountant Q&A - ~20th: final closed accounts

The close status is tracked per (entity, period) in the period_close table (see above). Status progresses: open → in_close → closed.

Late-booking handling. An invoice dated January can arrive in March after January is closed. The GL model handles this with two dates per entry: - accounting_date — the economic date (January) - booking_date — when the entry was recorded in the system (March)

Dual-view reporting pattern. Every report in the system supports two views:

  1. "As-reported" (default) — shows the numbers as they were when the period was closed. Uses the GL mapping version in force at the close date, and only entries that had been booked at or before the close date. Numbers never change. This is the audit-grade view.
  2. "Restated" (toggle) — shows what the numbers would look like with current knowledge. Uses the current/latest GL mapping (applied retroactively) and all entries by accounting_date, including late-arriving ones.

The delta between the two views is surfaced explicitly so finance can investigate what changed since close.

This pattern applies uniformly to P&L / BS / CF at any scope, per-entity reports, consolidated reports, and historical comparisons.

Design rule: the close snapshot is preserved as a fact (not re-derived from current data). Reports query the snapshot for "as-reported" and query live GL with accounting_date filter for "restated."


5. Aggregation hierarchy (management reporting tree)

The legal ownership tree in §2 answers "who owns what". The management reporting tree answers "what do we want to see on a dashboard". They are different trees, both first-class, neither derived from the other.

Detail level

  • Practice groups (each is an operating entity + its SPFPL rolled up): THS, CDA, PDS+PDSh, VSM+VSMh. Future acquisitions expand this list (5-10 new practices/year expected).
  • Central entities (shown individually): SDV, SUI, CHC. Future: AST-holding part, SDT-holding part when dimension split is implemented.
  • Belgium entities (shown individually or combined): AST (full), SDT (full). Future: split clinic/holding via the LOCATION/PROJECT dimension.

Subtotals (dynamic)

Subtotal Members Notes
Existing FR practices FR practice groups where acquisition_fy < current_fy
New FR practices FR practice groups where acquisition_fy == current_fy Graduate to "existing" at next Oct 1.
Vintage cohorts FR practice groups grouped by acquisition_fy Permanent tag for cohort analysis (FY26 vintage, FY27 vintage, ...).
FR practices total Existing + New
Central costs total SDV + SUI + CHC Future: + AST/SDT holding parts.
Belgium entities total Full AST + SDT Future: clinic/holding/combined split.

Group totals

Total Members Notes
Group excl. BE FR practices total + Central costs total The "France-core" view. A permanent management preference, not a workaround.
Group incl. BE Group excl. BE + Belgium total The "Groupe Suffren" total — matches the Conso. sheet in the current Excel.

Design rules

  1. "New" is derived, not tagged. Computed from entity.acquisition_date vs current FY start (Oct 1). No manual maintenance.
  2. Vintage is permanent. entity.acquisition_fy never changes — it's the FY in which the entity was first consolidated. Used for cohort views.
  3. The entity list grows. 5-10 new practice acquisitions per year. Each new acquisition auto-creates a new practice group scope. Adding a new practice must NOT require manual scope configuration beyond creating the entity row.
  4. Belgium is quarantined from the France view because AST/SDT mix clinic and holding activity. Long-term, once the LOCATION/PROJECT dimension split is implemented, Belgium clinics could optionally join "Practices total" and Belgium holding could join "Central total." But "Group excl. BE" remains a permanent view regardless.
  5. Country is a filtering dimension. "Excl. BE" is not a workaround — it's a permanent management preference for viewing France-core performance without Belgian mix effects.
  6. Scopes are self-expanding. The aggregation engine computes scope membership from entity attributes (country, entity_type, acquisition_date, SPFPL→practice relationship) — not from manually-maintained membership tables. Adding a new entity with the right attributes automatically places it in the correct scopes.
  7. Practice group is the default view. The roll-up of PDS+PDSh (or VSM+VSMh) into one line is the default for practice managers. Finance users get a toggle to see per-entity detail.

Implications for aggregation_scopes table

Consider replacing the manual aggregation_scope_members M2M with computed scope membership based on entity attributes + rules. This eliminates the maintenance burden of manually adding every new acquisition to multiple scope membership rows. The scope definitions become rules (e.g., "all entities where country=FR AND entity_type=operating_practice AND acquisition_fy < current_fy") rather than enumerated member lists.

If keeping explicit membership tables, require that entity creation auto-populates all relevant scope memberships via a signal/hook.

The aggregation tree has no direct relationship to the legal tree. Two examples:

  • PDSh is the legal parent of PDS (§2), but in the practice group view they're siblings rolled into one line. The management view flattens the legal nesting.
  • SDT is the legal parent of SDV, THS, CDA, PDSh, VSMh, but SDT belongs to "Belgium entities total" while its children are in "FR practices total" or "Central costs total."

Any implementation that tries to express one tree as a transform of the other will fail on the first edge case. Keep them separate.


6. Intercompany elimination rules

Eliminations are declarative, stored in intercompany_elimination_rules (see §4). The consolidation engine applies the rules that match the requested scope level at query time.

Current known rules

Rule code Fires at scope ≥ What eliminates Why
SDV_PRACTICE_SERVICES Group excl. BE SDV revenue from "central costs reinvoicing" ↔ practice "Central costs reinvoicing" expense; corresponding receivable/payable pair on BS SDV invoices the practices for shared services.
SDT_VSMh_CB_INTEREST FR practices total (contains both parties) SDT "Financial incomes" (interest income on CB) ↔ VSMh "Financial expenses" (interest expense on CB) VSMh CB interest flows up to SDT. Eliminated at any scope containing both parties.
SUI_AST_CL_INTEREST Belgium entities total SUI "Financial incomes" (interest on SUI→AST CL) ↔ AST "Financial expenses" CL interest flows up. Eliminated at any scope containing SUI and AST.
AST_SDT_INVESTMENT Belgium entities total AST "Gross Financial fixed assets" (titres de participation SDT) ↔ SDT "Share Capital" + reserves Equity-method elimination for the 49%+51% CSa/CSb structure.
PDSh_PDS_INVESTMENT PDS practice group PDSh "Gross Financial fixed assets" (titres de participation PDS) ↔ PDS "Share Capital" + reserves Same as above for PDSh→PDS.
VSMh_VSM_INVESTMENT VSM practice group VSMh "Gross Financial fixed assets" ↔ VSM "Share Capital"
SDT_OP_PRACTICE_INVESTMENTS FR practices total SDT investments in THS/CDA/PDSh/VSMh ↔ their equity Top-level equity-method elimination for the Belgian holding's stakes.

Future rules

  • CHC_SERVICE_FEES — CHC's dual service-co role (to be activated when CHC starts invoicing SDV / practices)
  • CHC_AST_DIRECT_CL — new CL flow from CHC direct to AST, parallel to SUI→AST
  • Per-conversion-event rules — when CL/CB flip to equity, the existing *_INTEREST rules close on the conversion date and new dividend / equity rules open

Who maintains these

Rules are config, not code. The finance team edits them via an admin UI (or YAML file, see docs/finance-roadmap.md §Decisions needed). Adding a new rule does not require a Django migration, only a database row.

The consolidation engine validates that every elimination is balanced (the two sides net to zero at the requested scope) and reports unbalanced eliminations as warnings on the consolidation run.


7. Resolved questions

All questions from the initial draft have been resolved.

  1. CHC role modelling — RESOLVED. One entity row. The dual role (top holding + occasional service co) is expressed via aggregation scope membership, GL account / report line attribution (service fee income on specific P&L lines; holding activity on others), and separate intercompany elimination rules for each role.

  2. Post-conversion tax treatment — RESOLVED. A lightweight fiscal_integration_group concept has been added to §4. It is a declared fact (not mechanically derivable), tracked with effective_from/effective_to. Current group: VSMh+VSM. The conversion preview feature warns when a conversion event would remove an entity from its fiscal integration group.

  3. Individual holders for finance — RESOLVED. Individual holders are populated from day one — both roadmaps ship Phase 1 together. No "aggregated bucket" rows in the schema. Finance views aggregate individual holders per entity at query time. The holders table stores individual person data; the ownership_links table stores the derived entity-to-entity aggregate. See updated §4.

  4. CSc acquisitions — RESOLVED. The system supports any post-acquisition outcome — hold the CSc as-is, merge into existing class, transform to another class, or create new classes. A CSc acquisition is modelled as one or more ownership_events that close old ownership_links and open new ones. Finance consolidation just sums FR% across all links for a given parent→child pair (doesn't care about the share class breakdown). Cap table tracks the full class-level detail.

  5. Fiscal year alignment — RESOLVED. Management reporting FY is Oct 1 – Sept 30 across all entities and countries. The "FYE 31/12" label in the current Excel is a stale typo — ignore it. Legal FY also aligns to Oct-Sept for all current entities. New acquisitions transition to Sept 30 close via shortened/extended transitional year. The entities table includes acquisition_date and consolidation_start_date to handle transitional periods. 5-10 new acquisitions expected per year.

  6. Practice ↔ Entity mapping — RESOLVED. Single nullable FK on Practice pointing to Entity (practice.entity_id). One Practice per operating entity (1:1 for operating practices). SPFPL holdings, service co, centrals have NO linked Practice. The SPFPL→operating relationship is already captured in ownership_links.

  7. Minority bucket granularity — RESOLVED. No aggregated bucket rows in the schema. Individual holders populated from day one (see Q3). Finance views GROUP BY entity to show aggregate minority percentages. The "~1% practicing dentists" line in a P&L minority-interest calculation is computed by summing all individual holder FR% for that entity at query time.

  8. Chart-of-accounts harmonization — RESOLVED. One gl_mapping_rule table with a country column — not separate per-country tables. All countries map to the same 47 report-line target catalog. Mapping is time-versioned (effective_from/effective_to). Both "as-reported" (mapping version active at close) and "restated" (current mapping retroactively) views are supported. See §4a for the dual-view pattern.

  9. Monthly vs daily granularity — RESOLVED. Daily granularity is available across the board — both Pennylane (GL entries have dates) and non-Pennylane entities (full journal entries with dates). The unified GL model stores entries at date-level (accounting_date per entry, plus booking_date for when it was recorded). Monthly views are query-time aggregation. See §4a for the close workflow and dual-date model.


8. Implementation notes

This document is design, not code. Implementation notes:

  • A new Django app apps/entities/ owns the entities, ownership_links, holders, ownership_events, aggregation_scopes, intercompany_elimination_rules, fiscal_integration_groups, fiscal_integration_memberships, period_close tables. Share class and register tables (share_classes, share_register, bond_register) are owned by the cap table roadmap. Do not scatter entity-model tables across apps/practices/, apps/budgets/, or a hypothetical apps/finance/.
  • The apps/practices/models.py:Practice model gains a nullable entity FK pointing into entities. A data migration populates it from internal_code.
  • The existing apps/core/models.py:AuditModel is the base class for all entity-model tables — gives us created_at, updated_at, created_by, updated_by for free.
  • Seed data (entities, share classes, ownership links, aggregation scopes, elimination rules) is loaded via a Django management command seed_group_entities. Committed to git. Re-runnable (idempotent).
  • The finance-roadmap Phase 1 ticket is responsible for building the apps/entities/ module and seeding it. No Pennylane sync or report feature should be built on top of it before it exists.

Appendix A: Relationship to existing Aletheia concepts

Aletheia concept Role in finance model
apps.practices.Practice Operational unit — stays as-is. Gains a nullable entity FK. Not all entities have a Practice (SDV, holdings have none).
apps.dentists.Dentist + DentistContract Source for revenue-per-dentist and associate-fees-per-dentist joins. Matched to Pennylane RETRO <NAME> accounts by name (with manual override UI for mismatches).
apps.budgets.BudgetVersion Per-practice today. In Phase 2 gains an entity FK; BudgetVersion can live at Practice or Entity level (the latter is used for service cos, holdings).
apps.core.AuditModel Base for all new entity-model tables.
apps.core.permissions.feature_required New feature strings (finance_view, finance_manage, consolidation_view, cap_table_manage, etc.) map to existing Finance Manager, CFO, Legal/HR Manager Django groups.
apps.annuaire.AnnuaireOrganization Reference data, not internal entity registry. Has SIRET/SIREN/forme juridique for every FR-registered dental organization (via RPPS/FHIR). Can be used to auto-populate entities.siren/entities.siret for FR entities by lookup, but is not the authoritative source for our legal structure.
apps.imports.AbstractImporter Reusable base for the Excel GL import path (see finance roadmap §Multi-source financial data ingestion).

End of document. Changes to this file require review by both the finance roadmap owner and the cap table roadmap owner. Downstream roadmaps reference section numbers — avoid renumbering without a deprecation note.