Aller au contenu

Aletheia Cap Table & Equity Management Roadmap

Status: Phase 1 complete (2026-04-13), Phase 2 complete (2026-04-14), Phase 3 in progress (3.1 + 3.3 complete, 3.2 deferred) Audience: CFO, Legal/HR, executive team, engineering leads Related: docs/entity-model.md — shared foundation for entities, ownership, consolidation Sibling: docs/finance-roadmap.md — financial reporting, consolidation, dashboards


1. Executive summary

  • Today's cap table is a CFO-maintained spreadsheet. It is reliable but equity-only, cannot represent convertible instruments (CB/CL), and does not cover the central entities (CHC, SUI, AST, SDT). Every dentist hire or departure triggers a manual 6-document legal pack — time-consuming for Legal/HR and error-prone at scale.
  • High-volume, low-complexity movements dominate. French law requires every practicing dentist to hold shares. Each new hire → 1-share issuance + 6-document entry pack. Each departure → 1-share buyback + 3-document exit pack. The group processes ~20-40 of these movements per year across all practices, growing with acquisitions.
  • Rare but high-stakes significant movements need precision. CSc acquisitions (THS 20% FR, PDSh 16% FR), CL/CB conversion events, and new financing rounds are infrequent but impact consolidation, minority interest, and fiscal integration. Getting them wrong is expensive.
  • Convertible instruments are invisible in today's system. SDT's 100% FR claim on VSMh via CB, and SUI's 100% FR claim on AST via CL, are absent from the Excel cap tables. The new system treats equity and convertible debt uniformly from day one.
  • Legal/HR is a new persona for Aletheia. The platform has no document generation, no movement workflow, and no Legal/HR-specific permissions. This roadmap introduces all three.
  • The cap table shares its data model with the finance roadmap. Both roadmaps build on docs/entity-model.md. The cap table roadmap owns the detailed share register, bond register, and share class tables. The finance roadmap reads the derived ownership_links aggregate. Neither redefines the entity model.
  • Phase 1 delivers the register and migration. Share class tables, individual holders for all 11 entities, migration from CFO Excel + lawyer-held records, basic CRUD, cascade ownership view, permissions — the foundation for everything else.
  • Phase 2 delivered document automation (2026-04-14). The 6-document entry pack and 3-document exit pack are auto-generated from templates, with a guided movement workflow, document archive, and signed-file upload. All 6 features complete.
  • Phase 3 delivered dividends and notifications (2026-04-14). Dividend allocation computation with preference waterfall and Excel export (Feature 3.1), and notification/approval routing for movements (Feature 3.3). Cap table change simulation (Feature 3.2) is deferred to the future backlog — no immediate business trigger.
  • Future backlog. Cap table change simulation (originally Phase 3.2), conversion event workflow, partner self-service, e-signature (DocuSign), entity-scoped permissions, automated PDF conversion, and workflow enforcement are captured as deferred features — architecture supports them, no committed timeline.
  • Central entity migration is non-trivial. CHC, SUI, AST, SDT cap tables live at the group's lawyers in physical format. Collecting, digitizing, and validating these is a separate workstream with Legal — planned for Phase 1 but requiring dedicated effort.

2. Current state of Aletheia & Legal/HR workflows

Stack and conventions (cap-table-relevant)

  • Framework. Django 5.2, Python 3.13, PostgreSQL, Celery 5.6 + Redis, Bootstrap 5, HTMX. All apps under apps/. CBVs with mixins. Template skeletons for list/detail/form pages in templates/skeletons/.
  • Auth and permissions. Custom User model with email login. Feature-based permission system via @feature_required('feature_name') decorator and FeatureRequiredMixin. 9 Django groups (Admin, Operations Manager, Practice Manager, HR Manager, Finance Manager, M&A Manager, M&A User, Marketing Manager, Practitioner, Sync). Features are flat strings mapped to groups in apps/core/permissions.py.
  • Practice-level access. UserPracticeAccess M2M controls which users see which practices. MultiPracticeFilterMixin scopes views to the user's accessible practices.
  • Audit model. apps/core/models.py provides TimeStampedModel (created_at, updated_at), AuditModel (+ created_by, updated_by), and SoftDeleteModel (+ active flag, deleted_at). No django-auditlog or django-simple-history — audit is field-level, not event-sourced.

Legal/HR persona: implemented

Updated 2026-04-13. Cap table permissions are live. Four feature strings added: cap_table_view, cap_table_manage, cap_table_generate_docs, cap_table_approve_movements. Legal/HR Manager group configured with cap_table_view, cap_table_manage, cap_table_generate_docs. CFO group has cap_table_view, cap_table_manage, cap_table_approve_movements. All cap table views enforce these gates.

Document generation: implemented

Updated 2026-04-14. DOCX generation is live via docxtpl. 9 master templates committed to apps/captable/templates/legal_docs/ (6 entry, 3 exit). DocumentTemplate model tracks template metadata; GeneratedDocument model stores rendered DOCX files with version tracking, linked to movements. DocumentPackGenerator and DocumentContextBuilder services handle rendering. See §6 for architecture details and §8 for feature descriptions.

Workflow patterns: simple status + events

The codebase uses simple CharField(choices) for state management (no django-fsm). Examples: - CRM Prospect: stage pipeline (strategic_target → integrated) with StatusChange event logging - Import: status (pending → processing → completed/failed) with per-row tracking - Budget: status (draft → submitted → validated → archived)

The CRM's StatusChange model (records from_status, to_status, changed_by, timestamp, reason) is the closest pattern to what the movement workflow needs.

Existing entity/ownership concepts: implemented

Updated 2026-04-13. All entity and ownership models are now implemented. apps/entities/ owns the entity taxonomy, ownership links, holders, ownership events, aggregation scopes, elimination rules, fiscal integration, and period close. apps/captable/ owns share classes, share register, bond register, and cap table movements. apps/practices/Practice has a nullable FK to Entity.

Adjacent useful infrastructure

  • apps/annuaire/ has RPPS/FHIR-sourced AnnuaireOrganization records with SIREN/SIRET for every FR-registered dental organization — useful for auto-populating entity fields.
  • apps/annuaire/services/inpi.py already connects to the INPI RNE API — could support automatic legal-form and dirigeants lookup when onboarding new entities.
  • apps/imports/AbstractImporter provides dry-run mode, bulk operations, per-row tracking — reusable for cap table data migration.
  • apps/crm/services/ demonstrates the service-layer pattern for business logic.
  • apps/dentists/Dentist + DentistContract already holds the practitioner data that cap table holder records will reference.

Cap table artifacts (from reference/captables/)

7 FR operating entities have Excel cap tables maintained by the CFO. Coverage varies:

Entity Cap table Shareholder list Movement registry Notes
THS Yes Yes Yes Full trio — the reference template
VSM Yes Yes Yes Full trio
CDA Yes Cap table only
PDS Yes (8 snapshots, Feb 2023 – Apr 2026) Historical snapshots embedded in one file
PDSh Yes (minimal — 2 rows) SPFPL holding: 16.65% / 83.35% split
SDV Yes (minimal — 1 row) 100% Star Dental
VSMh Yes (minimal — 1 row) 100% OS by practicing dentist. CB not represented.

Central entities (CHC, SUI, AST, SDT): not in the folder. Currently tracked in physical format at the group's lawyers.

Cap table column structure

The standard "Table de capitalisation" has dual-rights columns per share class:

Column pattern Purpose
Associé Shareholder name (individual or entity)
Actions A / Actions B / Actions C Share count per class
%KDV (per class) Droits de Vote — voting rights percentage
%DF (per class) Droits Financiers — financial rights percentage
Total actions Sum across all classes
Total %KDV / Total %DF Aggregate voting and financial rights

Key observations from the data: - CVR and FR diverge dramatically. PDS example: Pierre Bodin holds 50.72% KDV but only 0.99% DF via Actions A; BL SPFPL holds 49% KDV but 99% DF via Actions B. The data model's independent CVR/FR cascades are confirmed as essential. - 1-share practicing-dentist pattern confirmed. New dentists receive exactly 1 share (or 10 shares post-split), conferring minimal voting/financial rights. - Class C exists at THS. 100 Actions C held by "STAR DENTAL BV" with 0% KDV, 80% DF — a non-voting preferred share class for economic rights.

Movement registry structure ("Registre de mouvement de titres")

The THS and VSM registries provide the canonical audit trail. Columns:

Column Content
Date Movement date
Titulaire (seller) Name + ID
Nombre de titres Quantity transferred
Nature du mouvement Movement type
Bénéficiaire (buyer) Name + ID
Observations Detailed narrative

Movement types observed: cession d'actions (share transfer), achat (purchase), transformation en SELAS (legal entity restructuring), division valeur nominale par 10/100 (share splits), conversion d'actions de préférence en actions ordinaires (class conversion), cession actions de préférence (preferred share transfer).

The VSM registry traces complex historical events: entity transformation → share split → progressive class conversions — confirming that the data model must handle more than simple buy/sell.

Shareholder list structure ("Liste des actionnaires")

Period-based ledger with opening/closing balances and movement columns:

Column Content
N° identification Sequential shareholder ID within entity
Noms Full legal name
Solde précédent titres Opening share count
Achat / Vente / Attribution / Divers Movement columns by type
Nouveau solde titres inscrits Closing share count
Contrôle du capital Movement notes with dates

Entry pack (6 documents): Produced for every new practicing dentist. Observed in 4 sample packs (PDS ×2, THS ×1, VSM ×1):

Doc Purpose Observed in
DUA (Décision Unanime des Associés) Unanimous shareholder decision authorizing entry PDS (SELAS form)
PV d'AG (Procès-Verbal d'Assemblée Générale) Shareholder meeting minutes authorizing entry VSM (alternate form)
ODM (Ordre de Mouvement de titres) Formal share transfer order — standardized format All entities
CEL (Convention d'Exercice Libéral) Practice agreement between dentist and entity All entities
Contrat de prêt Prêt participatif loan financing the share purchase PDS, THS
Courrier à l'Ordre Letter to OCD (Ordre des Chirurgiens-Dentistes) regulatory body PDS

Key variation: SELAS entities (PDS) use DUA; other forms may use PV d'AG (observed in VSM). The generator must handle this variation per entity legal form.

Exit pack (3 documents): Observed in 1 sample pack (PDS):

Doc Purpose
DUA (exit version) Confirms departure and loss of associate status (no agrément needed)
ODM Share buyback transfer order
Courrier OCD Departure notification to regulatory body

Exits are structurally simpler — no CEL (terminated, not created), no Contrat de prêt (no new financing).

Parameterization level

Documents are highly parameterized. The DUA alone requires: entity legal name, capital, RCS number, registered address, decision date, full shareholder list with legal names, new dentist's full civil details, statutory article references (vary by entity form), and signature blocks for all current shareholders. The ODM additionally requires: share class name, movement type, quantity (in words and numerals), from/to party details including addresses and IDs.

The Contrat de prêt and CEL are the most complex — multi-page contracts with compensation terms, repayment schedules, representations, and entity-specific clauses.

Current pain points

  1. Manual document assembly. Legal/HR produces each pack by editing Word templates, copying entity data, updating shareholder lists, adjusting dates — for every single movement.
  2. No central registry. FR operating entity cap tables are in Excel; central entity cap tables are at the lawyers. No single view of group ownership.
  3. Equity-only limitation. VSMh's 100% FR via CB is invisible in the Excel system. Minority interest calculations that depend on accurate FR cascades are structurally wrong without this.
  4. No time-versioned history. PDS's file contains 8 manual snapshots; THS/VSM have movement registries. But there's no automated "show me the cap table as of date X" capability.
  5. Movement frequency. With 11 entities and 5-10 new acquisitions per year, the group processes an estimated 20-40 practicing-dentist movements per year, plus occasional significant minority events. This volume is manageable manually today but will not scale.

4. Strategic framing

Why move cap table into Aletheia

  1. Single source of truth. Replace the CFO spreadsheet + lawyer-held physical records with one authoritative, time-versioned, auditable register that both Finance and Legal/HR read from.
  2. Document automation at scale. The 6-document entry pack and 3-document exit pack are the highest-frequency legal deliverable. Automating them saves Legal/HR hours per movement and eliminates copy-paste errors.
  3. Consolidation accuracy. The finance roadmap's minority interest calculations depend on accurate FR cascades. Today, the equity-only Excel system structurally cannot represent the CB/CL instruments that make the group's ownership structure work. The cap table must model convertible debt from day one.
  4. Growth readiness. 5-10 acquisitions per year means 5-10 new entities, each with their own share register, practicing-dentist bucket, and document pack templates. The manual approach does not scale.
  5. Audit trail. Shareholder disputes, regulatory inquiries, and due diligence for acquisitions all require a clean audit trail of who held what, when, and what documents were produced. Today this trail is fragmented across Excel files, physical binders, and email.

Relationship to the finance roadmap

The two roadmaps are independent in deliverables, coupled in data model:

Cap table roadmap (this doc) Finance roadmap (sibling)
Owns share_classes, share_register, bond_register, individual holder rows, movement workflows, document generation GL storage, consolidation engine, dashboards, cost analytics, Pennylane sync
Reads entities, ownership_links (which it computes and caches), fiscal_integration_groups ownership_links (cached CVR/FR %) for minority interest calculations
Writes Individual holder records → recomputes → ownership_links aggregate GL transactions, trial balances, mapping rules
Shared docs/entity-model.md — entities, ownership structure, consolidation rules Same

Sequencing: Both roadmaps ship Phase 1 together. The finance roadmap's Feature 1.1 (Entity model & admin) implements the shared apps/entities/ module. The cap table roadmap's Phase 1 implements the detail tables (share_classes, share_register, bond_register) and populates individual holders. The ownership_links table is the handoff point — cap table writes it, finance reads it.

Non-goals

  • Not rebuilding accounting. Cap table events have accounting implications (e.g., capital increase → equity on BS), but the cap table roadmap does not post journal entries. Finance handles that via GL.
  • Not a full legal case management system. The roadmap automates document generation and tracks movement status, but does not manage litigation, contract negotiation, or regulatory filings beyond the OCD letter.
  • Not e-signature. Document generation produces DOCX (editable for Legal review). E-signature integration (DocuSign) is deferred to the future backlog — not committed to any phase.
  • Not a compliance engine. The system does not enforce or verify French dental regulatory compliance (e.g., "does this dentist actually hold a valid licence?"). It produces the documents that Legal/HR uses for compliance.
  • Not a CRM or M&A tool. apps/crm/ handles the acquisition pipeline. The cap table roadmap starts after an entity is acquired and integrated into the group structure.

5. Foundation reference

The single shared foundation for this roadmap is docs/entity-model.md. That document defines:

  • Share class vocabulary (§1): OS, CSa/b/c, CL, CB — and the independence of CVR and FR cascades
  • Legal ownership tree (§2): ASCII tree + canonical entity table for all 11 entities
  • Consolidation methodology (§3): full consolidation, FR-based minority interest, practicing-dentist bucket, CL/CB economic attribution, conversion events
  • Data model (§4): entities, ownership_links (derived/cached), holders, ownership_events, aggregation_scopes, intercompany_elimination_rules, fiscal_integration_groups, period_close
  • Aggregation hierarchy (§5): management reporting tree (separate from legal tree)
  • Intercompany elimination rules (§6): seed rules and engine design

Critical design decisions already resolved in the foundation:

  1. ownership_links.cvr_pct and fr_pct are computed/cached from the underlying share and bond registers — not independently maintained (§4).
  2. Share class and register tables are owned by this roadmap, not the foundation or the finance roadmap (§4 "Share class and register tables — cap table domain").
  3. Individual holders are populated from day one — both roadmaps ship Phase 1 together (§7 Q3).
  4. ownership_links rows are never updated in place — all changes go through ownership_events (§4).
  5. A service recompute_ownership_percentages(entity) derives cached values on any cap table event (§4).

This roadmap does not redefine these concepts. If you find an inconsistency between this file and the foundation, the foundation wins.


Requirements

The cap table roadmap must generate 9 distinct document types across entry and exit packs:

Document Entry Exit Variations
DUA (Décision Unanime des Associés) Yes Yes (exit version) Entity-form-specific (SELAS)
PV d'AG (Procès-Verbal d'AG) Yes Used instead of DUA for non-SELAS entities
ODM (Ordre de Mouvement de titres) Yes Yes Standard form, minimal variation
CEL (Convention d'Exercice Libéral) Yes Entity-specific compensation terms
Contrat de prêt Yes Entity-specific loan terms
Courrier à l'Ordre Yes Yes Departmental OCD address varies

Each document requires deep parameterization: entity legal data (name, capital, RCS, address, legal form), shareholder list, party civil details, share class names, quantities, dates, statutory references, and (for CEL/Contrat de prêt) compensation and financial terms.

Proposed architecture

Template engine: python-docx + docxtpl.

  • docxtpl (Jinja2 templating for DOCX) is the recommended library. It allows Legal to author master DOCX templates in Word (preserving formatting, logos, signatures) with {{ placeholder }} and {% for %} tags. Engineers do not need to touch the document layout.
  • python-docx underlies docxtpl and is available for any programmatic manipulation that Jinja2 cannot express (e.g., dynamically inserting tables for shareholder lists of variable length).
  • PDF conversion: deferred. DOCX-only generation for Phases 1-3. Legal produces PDF manually ("Save as PDF" in Word) when needed. Automated PDF conversion (via LibreOffice headless or similar) is reconsidered only when/if e-signature integration is built. Note: DocuSign (the group's e-signature provider) accepts DOCX directly, so the absence of automated PDF does not block future e-signature integration.

Template management:

apps/captable/
  templates/
    legal_docs/
      entry/
        dua_selas.docx          # DUA template for SELAS entities
        pv_ag_selarl.docx       # PV d'AG template for SELARL entities
        odm.docx                # ODM — standard form
        cel.docx                # CEL — per-entity variants via context
        contrat_pret.docx       # Loan contract
        courrier_ordre.docx     # Letter to OCD
      exit/
        dua_exit_selas.docx     # DUA exit version
        odm_exit.docx           # ODM exit version
        courrier_ocd_exit.docx  # Departure notification
  • Templates are committed to git, versioned alongside code.
  • Each template has a template version tracked in a DocumentTemplate model (id, template_type, entity_legal_form, version, file_path, effective_from). This supports template evolution without breaking historical document generation.
  • Legal/HR can request template changes via the normal development process. A future enhancement could allow template upload via admin UI.

Document generation flow:

  1. Movement initiated → movement workflow creates a CapTableMovement record with status initiated
  2. Generate pack → a service (DocumentPackGenerator) identifies which documents are required based on movement type + entity legal form
  3. For each document: load the correct template → populate the Jinja2 context from entity, holder, movement data → render DOCX
  4. Store generated documents as GeneratedDocument rows linked to the movement (DOCX files; PDF fields reserved for future automated conversion)
  5. Legal/HR reviews the DOCX, makes any manual tweaks, saves as PDF if needed, re-uploads the signed version
  6. Archive the final signed version per entity per movement

Context assembly:

A DocumentContextBuilder service assembles the rendering context for each document type:

  • Entity data: from entities table (name, capital, RCS, address, legal_form)
  • Shareholder list: from share_register filtered by entity + effective_date (for DUA/PV d'AG)
  • Party data: new/departing dentist details from holders + linked Dentist record
  • Movement data: share class, quantity, price, date from the CapTableMovement record
  • Financial terms: from the movement record or entity-level defaults (for Contrat de prêt)
  • Regulatory data: OCD departmental address looked up by entity's département

Not in scope for the generator:

  • E-signature integration — DocuSign is the known provider; deferred to future backlog. Document generation ends at DOCX; signing happens externally.
  • Automated PDF conversion — deferred to future backlog (see above). Legal converts to PDF manually when needed.
  • Automatic regulatory filing (the Courrier à l'Ordre is generated, but sending it to the OCD is manual)
  • Template WYSIWYG editor in the admin UI (templates are edited in Word, committed to git)

7. Phase 1 — Register & Migration

Theme. Stand up the cap table data infrastructure, migrate existing cap tables from Excel and lawyer-held records, provide the shareholder register and cascade ownership view that both Finance and Legal/HR need from day one.

Why this phase now. - The finance roadmap's Phase 1 needs accurate ownership data for minority interest and consolidation. Without individual holders populated, minority interest calculations use placeholder percentages. - The entity model (apps/entities/) is being built by the finance roadmap's Feature 1.1 — the cap table tables must be designed and seeded in parallel. - Central entity migration (from lawyers) has a long lead time. Starting the collection process in Phase 1 is essential even if full coverage takes months. - Every later cap table feature (document generation, movement workflows, simulation) depends on the register being accurate and populated.

Foundational work required. - New tables: share_classes, share_register, bond_register, cap_table_movements, generated_documents, document_templates (see feature descriptions below). - New Django app: apps/captable/ — dedicated app with its own views, forms, templates, and services. Cross-app FKs to apps/entities/ are standard Django practice. - New permission features in apps/core/permissions.py: cap_table_view, cap_table_manage, cap_table_generate_docs, cap_table_approve_movements. - New Django group: Legal/HR Manager with the above features. - The recompute_ownership_percentages(entity) service that derives ownership_links from the share/bond registers. - Data migration tooling: a CapTableImporter(AbstractImporter) for Excel-based migration.

Success metrics. - All 11 entities have accurate cap tables in Aletheia, matching the CFO spreadsheet within rounding tolerance. - All individual holders (practicing dentists, partner dentists, entity holders) are populated — no placeholder "bucket" rows. - The cascade ownership view correctly computes CHC's effective FR% in every operating entity, including through CL/CB instruments. - Finance's consolidated P&L minority interest line matches the ownership data in the register. - The movement registry for THS and VSM is fully migrated (historical movements preserved).

Key risks and open questions. - Central entity migration timeline. Collecting physical records from the lawyers requires Legal coordination. Risk: delay beyond Phase 1 completion → central entities entered with current-state-only, historical movements added progressively. - Data quality of existing Excel files. The CFO spreadsheet is the truth, but rounding, naming inconsistencies, and missing historical events may surface during migration. Plan for a reconciliation step. - Coordination with finance Phase 1. Both roadmaps need apps/entities/ to exist. The entity model and seed data should be the first deliverable, followed by cap table detail tables.

Phase 1 features

# Feature Status Tables read/written Users Complexity Finance dep?
1.1 Share class & register schema Complete share_classes, share_register, bond_register (write) L Yes (1.1)
1.2 Data migration — FR operating entities Complete share_register, holders, cap_table_movements (write) Finance, Legal/HR L Yes (1.1)
1.3 Data migration — central entities Complete Same as 1.2 Finance, Legal M Yes (1.1)
1.4 Cap table CRUD Complete All cap table tables (read/write) CFO, Legal/HR M No
1.5 Shareholder register view Complete share_register, bond_register, holders, share_classes (read) CFO, Legal/HR, Exec M No
1.6 Cascade ownership view Complete ownership_links, entities (read); ownership_links (write via recompute) CFO, Finance, Exec M Yes (1.1)
1.7 Movement audit trail Complete cap_table_movements, ownership_events (read/write) CFO, Legal/HR S No
1.8 Permission model for cap table Complete core.permissions (config) Admin S No

1.1 — Share class & register schema

Description. Design and implement the tables that docs/entity-model.md §4 delegates to the cap table roadmap: share_classes, share_register, and bond_register. These are the source-of-truth tables from which ownership_links.cvr_pct and fr_pct are derived.

share_classes — one row per share class per entity:

Column Type Notes
id BigAutoField
entity FK entities
code CharField(10) "OS", "CSa", "CSb", "CSc", "CL", "CB", custom
name CharField(100) "Actions Ordinaires", "Actions de Catégorie A", etc.
instrument_nature CharField equity or convertible_debt
shares_outstanding PositiveIntegerField null Total issued shares (equity only)
nominal_value DecimalField null Per-share nominal value (for equity)
cvr_weight DecimalField(7,4) Voting rights weight per share (relative to OS=1.0)
fr_weight DecimalField(7,4) Financial rights weight per share (relative to OS=1.0)
is_voting BooleanField Whether this class carries voting rights
effective_from Date
effective_to Date null Null = current
notes TextField blank
+ AuditModel fields

share_register (registre des titres) — one row per holder per share class per entity, time-versioned:

Column Type Notes
id BigAutoField
entity FK entities
holder FK holders Individual or entity holder
share_class FK share_classes
shares_held PositiveIntegerField Number of shares
effective_from Date
effective_to Date null Null = current holding
source_movement FK cap_table_movements null The movement that created this row
notes TextField blank
+ AuditModel fields

bond_register (registre des obligataires) — one row per convertible instrument per lender/borrower pair:

Column Type Notes
id BigAutoField
entity FK entities The borrower entity
holder FK holders The lender (entity-type holder)
share_class FK share_classes The CL or CB class
principal_amount DecimalField null Face value of the instrument
interest_rate DecimalField null Annual rate
contractual_cvr_pct DecimalField(7,4) Voting rights % granted by instrument
contractual_fr_pct DecimalField(7,4) Financial rights % (typically 100% for prêt participatif)
conversion_trigger TextField blank Description of conversion conditions
effective_from Date
effective_to Date null
source_movement FK cap_table_movements null
notes TextField blank
+ AuditModel fields

The recompute_ownership_percentages(entity) service is also part of this feature: - For equity instruments: (shares_held / shares_outstanding) × class_weight, summed per parent entity - For convertible debt: stated contractual_fr_pct / contractual_cvr_pct from bond_register - Writes to ownership_links (the cached aggregate that finance reads) - Triggered on any share/bond register change - Nightly reconciliation job verifies cached values match computed values

Complexity. L. The schema is substantial, the derivation logic must be thoroughly tested (especially the CL/CB edge cases), and seeding requires careful data preparation. Finance dependency. Yes — requires apps/entities/ from finance Feature 1.1 to exist first.

Implementation (2026-04-13). All three models implemented in apps/captable/models.py: ShareClass (db_table share_class), ShareRegisterEntry (db_table share_register), BondRegisterEntry (db_table bond_register). recompute_ownership_percentages(entity, as_of) in apps/captable/services/ownership.py — atomic transaction that derives equity CVR/FR from share register and debt CVR/FR from bond register, then creates/updates/closes OwnershipLink rows. All models have effective_from/effective_to time versioning and AuditModel inheritance. Unique constraint on active share classes per entity: (entity, code) where effective_to IS NULL.

1.2 — Data migration — FR operating entities

Description. Migrate the 7 FR operating entity cap tables from the CFO's Excel files into the new schema. Build a CapTableImporter(AbstractImporter) that reads the standard "Table de capitalisation" format and creates holders, share_register, and (where available) cap_table_movements rows.

Migration scope per entity:

Entity Cap table Shareholder list Movement registry Expected rows
THS Yes Yes Yes (full history) ~15 holders, ~30 movements
VSM Yes Yes Yes (full history) ~10 holders, ~20 movements
CDA Yes ~10 holders, current state only
PDS Yes (8 snapshots) ~10 holders, movements derivable from snapshots
PDSh Yes 2 holders
SDV Yes 1 holder
VSMh Yes + bond_register entry 1 OS holder + 1 CB lender (SDT)

For entities without a movement registry, the migration loads current state only. Historical movements can be back-filled later from PDS's snapshot history or from records at the lawyers.

Reconciliation. After migration, the importer runs recompute_ownership_percentages() for each entity and compares the resulting ownership_links to the seed data in docs/entity-model.md §4. Discrepancies are flagged for CFO review.

Complexity. L. The Excel formats vary across entities. The THS and VSM movement registries contain complex events (share splits, class conversions, entity transformation) that must be mapped to cap_table_movements event types. Reconciliation is non-trivial. Finance dependency. Yes — requires entities to be seeded.

Implementation (2026-04-13). manage.py import_captables command in apps/captable/management/commands/. Service layer in apps/captable/services/captable_import.py with: parse_multi_class_file() for CDA/PDS/THS/VSM (multi-class Excel with Actions A/B/C columns), parse_simple_file() for PDSh/SDV/VSMh (percentage-only, converted to 10000-share base). _resolve_holder() matches names to Holder records (entity mapping via ENTITY_HOLDER_NAMES dict, individual holders via honorific stripping + name splitting). Per-entity config in ENTITY_CONFIGS dict. VSMh gets a special-case CB bond entry for SDT. Post-import reconciliation via reconcile_entity() compares recomputed vs. seed ownership_links. Supports --entity <code> for single-entity import. 518 LOC of import tests.

1.3 — Data migration — central entities

Description. Migrate CHC, SUI, AST, SDT cap tables from the group's lawyers' physical records into Aletheia. This is a human-dependent process, not a pure engineering task.

Workstream: 1. Collect: Legal contacts the lawyers to request copies of all share registers, movement registries, and current shareholder lists for each central entity. Target: digital copies (scanned PDF or Excel extract). 2. Interpret: CFO + Legal review the collected documents to confirm share classes, holder identities, and current state. Engineer prepares a migration spreadsheet for validation. 3. Enter: Engineer uses the CapTableImporter or manual admin entry to populate the register. For convertible instruments (SUI→AST CL, SDT→VSMh CB), create bond_register entries with contractual terms. 4. Validate: CFO signs off on the migrated data.

Realistic expectation: Central entities have simpler cap tables (fewer holders, fewer movements) but the collection step has a long lead time. Plan for Phase 1 to include current-state entry for all 4 central entities, with historical movement back-fill as a Phase 1 tail item or early Phase 2 task.

Complexity. M. The technical work is modest (fewer holders than FR entities); the coordination and collection effort is the bottleneck. Finance dependency. Yes.

Implementation (2026-04-13). manage.py seed_central_captables command in apps/captable/management/commands/. Seeds CHC, SUI, AST, SDT with share classes, register entries, and placeholder individual holders (marked for replacement when lawyer records arrive). Supports --entity <code> for single-entity seeding. Idempotent (uses update_or_create). Effective date: 2024-10-01 (FY25 start). Runs recompute_ownership_percentages() after seeding. Note: central entities seeded with current-state data only. Historical movement back-fill remains a future task pending lawyer document collection.

1.4 — Cap table CRUD

Description. Admin and in-app UI for creating, viewing, updating, and deleting individual holder records per entity. Supports all share classes (OS, CSa/b/c, CL, CB, custom). All changes go through cap_table_movementsownership_events (never direct edits to share_register).

UI pattern: Follows existing Aletheia conventions (CBV + skeleton templates + HTMX): - List view: table of holders per entity, filterable by share class, sortable by holding size - Detail view: holder's full history across entities, current holdings, linked movements - Create/Edit: guided form that creates a cap_table_movement record (not a direct register edit) - The entity selector uses the existing MultiPracticeFilterMixin pattern, extended for entities

Complexity. M. Standard CRUD with the constraint that all edits are movement-mediated. Finance dependency. No (reads entity data, but doesn't depend on finance features).

Implementation (2026-04-13). Full CRUD implemented in apps/captable/views.py: - Holders: HolderListView (searchable, filterable by type, paginated 50/page), HolderDetailView (current + historical holdings), HolderCreateView, HolderUpdateView. - Share entries: ShareRegisterEntryCreateView, ShareRegisterEntryUpdateView, ShareRegisterEntryDeleteView. All trigger recompute_ownership_percentages() after save/delete. - Bond entries: BondRegisterEntryCreateView, BondRegisterEntryUpdateView. Also recompute after save. - Forms: HolderForm, ShareRegisterEntryForm (filtered to equity classes), BondRegisterEntryForm (filtered to convertible debt classes, entity holders only). - All views use FeatureRequiredMixin with cap_table_view (read) or cap_table_manage (write). - Deviation from plan: direct register edits are allowed through the CRUD views (not all edits are movement-mediated). Movement-mediated editing is a Phase 2 workflow constraint — Phase 1 supports both paths.

1.5 — Shareholder register view

Description. Per-entity view showing the current and historical shareholder register: - Current state: all holders, their share class, share count, CVR%, FR%, effective_from date - Historical snapshot: given any date, reconstruct the register as of that date's close - Share class summary: total shares outstanding per class, aggregate CVR/FR split - Practicing-dentist bucket: highlighted section showing the ~1% OS practicing-dentist holders

This is the in-app replacement for the CFO's "Table de capitalisation" Excel sheet.

Complexity. M. The time-versioned query (WHERE effective_from <= date AND (effective_to IS NULL OR effective_to > date)) is straightforward but must be thoroughly tested for edge cases (same-day movements, backdated corrections). Finance dependency. No.

Implementation (2026-04-13). EntityRegisterView in apps/captable/views.py + entity_register.html template. Shows share classes table, holdings register (holders × classes grid with computed CVR%/FR%), and bond register. Supports historical snapshot via as_of_date query parameter. Helper _active_at(qs, as_of) filters time-versioned querysets. _build_register_context() computes full register data with aggregated percentages per holder. RegisterListView lists all active entities with class/holder/bond counts.

1.6 — Cascade ownership view

Description. Computes and displays "CHC effectively owns X% FR and Y% CVR in entity Z" for any entity in the group tree. The cascade walks the ownership_links graph, multiplying CVR/FR percentages at each hop.

Implementation: - A service compute_cascade_ownership(from_entity, to_entity, as_of_date) traverses the ownership graph - Handles both equity and convertible_debt links (CL/CB route FR through economic_claim_pct, not through equity %) - Returns: effective_cvr_pct, effective_fr_pct, and the path taken (e.g., CHC → SUI → AST → SDT → THS) - UI: a tree visualization (or table) showing CHC's effective stake in every entity at the bottom of the tree

Edge cases that must work: - SUI → AST: 0% CVR, 0% equity, but 100% FR via CL (prêt participatif). The cascade must attribute FR through the debt instrument. - SDT → VSMh: same pattern via CB. - Multiple paths: CHC → SUI → AST → SDT → THS. The cascade multiplies FR% at each hop correctly (100% × 100% × 100% × 80% = 80%).

Complexity. M. The graph traversal is non-trivial due to the CL/CB edge cases. Unit tests must cover all 11 entities. Finance dependency. Yes — reads ownership_links, which are populated by the cap table's recompute_ownership_percentages() and seeded by the finance roadmap's entity model.

Implementation (2026-04-13). CascadeOwnershipView in apps/captable/views.py + cascade.html template. Service layer in apps/captable/services/cascade.py: - compute_cascade_tree(root_entity, as_of) — DFS from root, returns list[CascadeNode] with entity, effective_cvr, effective_fr, path, depth, link. For equity links: multiplies link.fr_pct. For convertible debt: multiplies link.economic_claim_pct. - compute_cascade_ownership(from_entity, to_entity, as_of) — point-to-point cascade. - CascadeNode dataclass with all cascade data. - UI: root entity selector (defaults to CHC), as_of date picker, tree/table visualization with depth/path display. - 309 LOC of cascade tests covering depth, paths, effective percentages, CL/CB handling, historical views.

1.7 — Movement audit trail

Description. Every cap table change is logged as a cap_table_movements row with full audit detail:

Column Type Notes
id BigAutoField
entity FK entities
movement_type CharField(choices) issuance, transfer, buyback, split, conversion, class_change, capital_increase, capital_reduction, entity_transformation
movement_date Date Effective date of the movement
from_holder FK holders null Source (for transfers, buybacks)
to_holder FK holders null Destination (for issuances, transfers)
share_class FK share_classes
shares_moved PositiveIntegerField
price_per_share DecimalField null
total_amount DecimalField null
description TextField Detailed narrative (mirrors "Observations" column in Excel registry)
status CharField(choices) initiated, approved, executed, documented, archived
approved_by FK User null
approved_at DateTimeField null
ownership_event FK ownership_events null Links to the shared foundation's event table
notes TextField blank
+ AuditModel fields

This model is the in-app equivalent of the "Registre de mouvement de titres" Excel sheet. In Phase 2, document generation will attach generated documents to each movement.

Complexity. S. Schema is clear; the movement types are enumerated from the Excel registries. Finance dependency. No.

Implementation (2026-04-13). CapTableMovement model in apps/captable/models.py (db_table cap_table_movement). All planned fields implemented. 9 movement types: issuance, transfer, buyback, split, conversion, class_change, capital_increase, capital_reduction, entity_transformation. 5-stage status: initiated → approved → executed → documented → archived. source_movement FK added to both ShareRegisterEntry and BondRegisterEntry for audit linkage. MovementListView, MovementDetailView, MovementCreateView, MovementUpdateView in views. Templates: movement_list.html, movement_detail.html, movement_form.html. 282 LOC of movement tests. Deviation: added entity_transformation movement type (not in original spec) to support historical VSM registry events. shares_moved is nullable IntegerField (not PositiveIntegerField as planned) to handle edge cases.

1.8 — Permission model for cap table

Description. Add new feature strings and a new Django group for cap table access:

Feature Who gets it Purpose
cap_table_view CFO, Finance Manager, Legal/HR Manager, Admin Read access to all cap table data across all entities
cap_table_manage CFO, Legal/HR Manager, Admin Create/edit holder records, initiate movements
cap_table_generate_docs Legal/HR Manager, Admin Generate document packs (Phase 2, but permission defined now)
cap_table_approve_movements CFO, Admin Approve movements before execution

New group: Legal/HR Manager with features: cap_table_view, cap_table_manage, cap_table_generate_docs, dentists_manage, dentists_view, dashboard_group.

Permission model: Role-based only for all committed phases. CFO, Legal/HR Manager, and Admin see all cap table data; everyone else sees nothing. Entity-scoped permissions (partner sees only their entities) are deferred to the future backlog alongside partner self-service.

Complexity. S. Extends the existing FEATURE_AREAS dict and adds one new group. The infrastructure is already in place. Finance dependency. No.

Implementation (2026-04-13). All four permission features added to apps/core/permissions.py: cap_table_view, cap_table_manage, cap_table_generate_docs, cap_table_approve_movements. Groups configured: Legal/HR Manager (cap_table_view, cap_table_manage, cap_table_generate_docs, dentists_manage, dentists_view), CFO (cap_table_view, cap_table_manage, cap_table_approve_movements, finance_manage, finance_view), Admin (all). All cap table views enforce feature gates via FeatureRequiredMixin.

Phase 1 — what it explicitly does not include

  • ~~No document generation~~ → delivered in Phase 2 (Features 2.1–2.3)
  • ~~No movement workflow with approval chains~~ → delivered in Phase 2 (Feature 2.4)
  • ~~No dividend allocation computation~~ → delivered in Phase 3 (Feature 3.1)
  • No cap table change simulation (deferred to future backlog)
  • ~~No notification & workflow routing~~ → delivered in Phase 3 (Feature 3.3)
  • No e-signature integration (future backlog)
  • No partner self-service portal (future backlog)
  • No conversion event workflow (future backlog — data model supports it; workflow deferred)

8. Phase 2 — Document Automation & Movement Workflows

Phase 2 complete (2026-04-14). All 6 features delivered. Document generation, entry/exit packs, movement workflow, document archive, and significant minority movements are live.

Theme. Automate the high-volume dentist entry/exit document packs and build guided workflows for all movement types, turning Legal/HR from document assemblers into document reviewers.

Why this phase now. - Phase 1 delivered the register — accurate data about who holds what. Phase 2 makes that data actionable for Legal/HR. - The 6-document entry pack is the most frequent legal deliverable. Automating it saves hours per movement and eliminates copy-paste errors that could have legal consequences. - With 5-10 new acquisitions per year, each bringing new practicing dentists, the document volume is growing. Automation must land before the volume becomes unmanageable. - The movement workflow provides the approval chain and audit trail that compliance requires.

Foundational work delivered. - python-docx + docxtpl added to dependencies - 9 DOCX master templates (6 entry, 3 exit) committed to apps/captable/templates/legal_docs/ - DocumentTemplate, GeneratedDocument models in apps/captable/models.py - DocumentPackGenerator and DocumentContextBuilder services in apps/captable/services/docgen.py - MovementStatusChange model + transition_movement() service in apps/captable/services/workflow.py

Success metrics. - Legal/HR generates a complete 6-document entry pack in under 5 minutes (vs. 1-2 hours today). - Generated documents pass Legal review without structural errors (data in the right fields, correct entity details, complete shareholder lists). - Every movement has a complete audit trail: who initiated, who approved, which documents were generated, which were signed and archived. - Exit packs (3 documents) are generated with the same reliability.

Key risks — resolved. - Template authoring. Templates authored with Jinja2 placeholders and committed to git. Legal-form-specific routing (SELAS → DUA, default → PV AG) is automatic. - Template variations. 9 template files cover all current entity forms. Per-entity-form variants handled via context variables, not separate template files.

Phase 2 features

# Feature Status Tables read/written Users New templates/integrations Complexity Finance dep?
2.1 Document template engine Complete document_templates, generated_documents (write) Legal/HR docxtpl M No
2.2 Entry pack generation Complete All cap table + entity tables (read); generated_documents (write) Legal/HR 6 DOCX master templates L No
2.3 Exit pack generation Complete Same as 2.2 Legal/HR 3 DOCX master templates M No
2.4 Movement workflow Complete cap_table_movements, movement_status_change (read/write) Legal/HR, CFO M No
2.5 Document archive & versioning Complete generated_documents (read/write) Legal/HR, CFO FileField + media storage S No
2.6 Significant minority movements Complete share_register, bond_register, cap_table_movements (read/write) CFO, Legal/HR L No

2.1 — Document template engine

Description. The reusable infrastructure for generating DOCX documents from templates. Implements the architecture described in §6:

  • DocumentTemplate model: tracks template type, entity legal form, version, file path, effective_from
  • DocumentPackGenerator service: given a movement, determines which documents are needed, generates each one
  • DocumentContextBuilder service: assembles the Jinja2 rendering context from entity, holder, movement, and share register data
  • GeneratedDocument model: stores each generated DOCX file, linked to the movement. PDF fields reserved for future automated conversion.

This is infrastructure, not user-facing. The user-facing features are 2.2 (entry pack) and 2.3 (exit pack), which call this engine.

Complexity. M. The template engine itself is straightforward (docxtpl handles the rendering); the context builder must handle all the entity/holder/movement data variations correctly. Finance dependency. No.

Implementation (2026-04-14). All four planned components implemented: - DocumentTemplate model in apps/captable/models.py: tracks type (9 choices), entity legal form, version, file path, effective_from/to. Template types cover DUA SELAS, PV AG, ODM, CEL, Contrat de prêt, Courrier Ordre, DUA exit, ODM exit, Courrier OCD exit. - GeneratedDocument model: stores generated DOCX via FileField, with pdf_file and signed_file fields reserved. Version tracking via auto-increment. Status: draft / final. - DocumentContextBuilder in apps/captable/services/docgen.py: builds Jinja2 context from entity, movement, holder, and register data. Includes number_to_words_french() utility and format_date_french() for document formatting. - DocumentPackGenerator in apps/captable/services/docgen.py: template selection with legal-form-specific routing (SELAS → DUA, default → PV AG), docxtpl rendering, version auto-increment on regeneration.

2.2 — Entry pack generation (6 documents)

Description. Given a "new practicing dentist entry" movement (from 1.7), generate the complete 6-document pack:

  1. DUA or PV d'AG (based on entity legal form): populates entity legal data, full current shareholder list, new dentist details, statutory references, decision date, agenda items
  2. ODM: share class name, quantity, from/to parties with civil details, movement date
  3. CEL: entity and dentist details, practice address, start date, compensation terms (from movement record or entity defaults)
  4. Contrat de prêt: lender (typically existing majority shareholder) and borrower (new dentist) details, loan amount, interest rate, repayment terms
  5. Courrier à l'Ordre: entity letterhead, departmental OCD address, decision date, list of enclosed documents

UI flow: 1. User navigates to entity → "New dentist entry" action 2. Form collects: dentist (FK to apps/dentists/Dentist or new entry), effective date, share class (defaults to OS/CSa), shares (defaults to 1), price, transferring shareholder, loan terms (optional) 3. Preview: shows the movement summary and lists the 6 documents to be generated 4. Generate: creates cap_table_movement + calls DocumentPackGenerator → produces 6 DOCX files 5. Review: Legal/HR downloads the DOCX files, reviews, makes any manual edits 6. Finalize: Legal/HR uploads the signed versions; movement status → documented

Complexity. L. 6 distinct templates, each with significant parameterization. The CEL and Contrat de prêt are multi-page contracts with entity-specific clauses. Template authoring alone is a substantial sub-task. Finance dependency. No.

Implementation (2026-04-14). All 6 DOCX master templates committed to apps/captable/templates/legal_docs/entry/: dua_selas.docx, pv_ag.docx, odm.docx, cel.docx, contrat_pret.docx, courrier_ordre.docx. DentistEntryView + DentistEntryForm implement the guided UI flow. DUA/PV AG selection is automatic based on entity legal form (SELAS → DUA). Movement type: issuance. Pack generation creates all 6 documents in one call via DocumentPackGenerator. Deviation: from_holder (cédant) field added to the entry form for issuance-from-existing-holder scenarios — not explicitly in the original plan but required for accurate ODM and DUA generation.

2.3 — Exit pack generation (3 documents)

Description. Given a "practicing dentist departure" movement, generate the 3-document exit pack:

  1. DUA (exit version): confirms departure, recognizes loss of associate status, references CEL termination
  2. ODM: share buyback transfer order (typically back to the majority shareholder)
  3. Courrier OCD (exit version): departure notification to the regulatory body

UI flow: Similar to 2.2 but simpler — fewer fields to collect (no loan terms, no CEL). Departing dentist selected from current holders; receiving shareholder defaults to majority holder.

Complexity. M. 3 templates, structurally simpler than entry. Reuses the engine from 2.1. Finance dependency. No.

Implementation (2026-04-14). All 3 DOCX master templates committed to apps/captable/templates/legal_docs/exit/: dua_exit.docx, odm_exit.docx, courrier_ocd_exit.docx. DentistExitView + DentistExitForm implement the UI. Departing dentist selected from current holders; movement type: buyback. No deviations from plan.

2.4 — Movement workflow

Description. A guided status workflow for cap table movements:

initiated → pending_approval → approved → executed → documented → archived
            rejected (with reason)
  • Initiated: Legal/HR creates the movement record (manual or via entry/exit form)
  • Pending approval: Routed to CFO (or configured approver) for sign-off
  • Approved: CFO confirms; share register update is authorized
  • Executed: System updates share_register rows, triggers recompute_ownership_percentages(), creates ownership_events row
  • Documented: Document pack generated and reviewed; signed versions uploaded
  • Archived: All documents final; movement is immutable

Each status transition is logged as a MovementStatusChange row (same pattern as CRM StatusChange): from_status, to_status, changed_by, timestamp, reason.

Complexity. M. The status model is straightforward (existing CRM pattern). The executed step — actually updating the register and recomputing ownership — is the critical transition that must be transactional. Finance dependency. No (but the recompute_ownership_percentages() updates ownership_links, which finance reads).

Implementation (2026-04-14). Full state machine in apps/captable/services/workflow.py: - VALID_TRANSITIONS dict defines the complete graph including rejected → initiated (resubmission). - transition_movement() validates transitions, logs each as a MovementStatusChange row (from_status, to_status, changed_by, timestamp, reason), and handles side-effects. - Side-effect on executed: creates OwnershipEvent and triggers recompute_ownership_percentages(). - 7 transition views: submit, approve, reject, execute, documented, archive, resubmit. - MovementRejectForm enforces mandatory rejection reason. - Permission split: cap_table_manage for submit/execute/documented/archive/resubmit; cap_table_approve_movements for approve/reject. - MovementStatusChange model in apps/captable/models.py (db_table movement_status_change), migration 0004. - Comprehensive test suite in tests/test_workflow.py covering all transitions, permissions, and view integration. Deviation: added rejected → initiated resubmission path (not in original diagram but a natural extension). The original plan showed only 6 statuses; rejected was mentioned as a branch but is now a first-class status with its own transition back. Deferred to Phase 3: notification routing on status transitions (planned as Feature 3.3). The workflow tracks status changes but does not send emails or notifications.

2.5 — Document archive & versioning

Description. Per-entity, per-movement document archive: - Each movement has a set of GeneratedDocument rows (one per document type) - Each document has: DOCX (generated draft), DOCX or PDF (signed/final upload by Legal/HR) - Version tracking: if a document is regenerated (e.g., after a correction), the previous version is preserved - List view: per entity, chronological list of all movements and their document packs - Download: bulk-download a complete pack as a ZIP

Complexity. S. Standard file storage + versioning. Uses Django's FileField with the existing media storage configuration. Finance dependency. No.

Implementation (2026-04-14). EntityDocumentArchiveView provides per-entity chronological archive of all movements and their document packs. MovementDocumentsView shows latest version of each document type per movement plus version history. PackDownloadView + build_pack_zip() implement bulk ZIP download of complete packs. Version tracking works via GeneratedDocument.version auto-increment on regeneration. Extra (not in plan): SignedFileUploadView allows Legal/HR to upload signed/final versions of documents, transitioning document status from draft to final. This closes the document lifecycle loop — planned implicitly (the plan mentioned "Legal/HR uploads the signed versions") but not broken out as a separate feature.

2.6 — Significant minority movements

Description. Support for movement types beyond the standard 1-share practicing-dentist entry/exit:

  • CSc acquisition: Group acquires CSc shares from a partner dentist (e.g., THS 20% FR, PDSh 16% FR). Multiple share classes may be involved. The movement closes one share_register row and opens new ones. Document packs differ (no CEL/Contrat de prêt; may need a share purchase agreement instead).
  • Share transfer between existing holders: Partner A sells to Partner B. ODM + DUA/PV d'AG.
  • Capital increase / capital reduction: New share issuance or buyback that changes total shares outstanding. Updates share_classes.shares_outstanding.
  • New financing event: New CL or CB instrument. Creates a bond_register row.

Each of these is a distinct movement_type in cap_table_movements with its own document requirements (some standard, some requiring custom document templates to be added over time).

Complexity. L. The variety of movement types is high, and each has different data requirements and document outputs. Phase 2 should cover the most common significant movements; truly exotic events (entity mergers, cross-border transfers) are handled manually. Finance dependency. No.

Implementation (2026-04-14). Three dedicated views/forms for significant movements: - ShareTransferView: transfer between existing holders (ODM + DUA/PV AG + Courrier à l'Ordre auto-generated). - CapitalChangeView: capital increase or reduction — updates share_classes.shares_outstanding and creates/closes share_register rows. - FinancingEventView: new CL or CB instrument — creates a bond_register entry. Each creates a CapTableMovement and auto-generates the appropriate document pack (DUA/PV AG + ODM + Courrier à l'Ordre for transfers; same pattern for capital changes). Deviation: CSc acquisition is not a separate flow — it is handled via the existing share transfer or entry flow depending on context. The plan listed it as a distinct operation, but in practice it maps to a standard transfer with CSc as the share class. No separate form was needed. Deviation: new financing events reuse TYPE_ISSUANCE movement type with a convertible debt share class, rather than a dedicated financing movement type. The data model supports this cleanly since share classes already distinguish equity from convertible debt.

Phase 2 — what was deferred

  • Automated PDF conversion: deferred as planned. Legal produces PDF manually via "Save as PDF" in Word.
  • ~~Notification on workflow transitions:~~ delivered in Phase 3 (Feature 3.3, 2026-04-14).
  • Automatic register update on executed transition: the executed side-effect creates an OwnershipEvent and recomputes ownership percentages, but does not automatically create/close share_register rows from the movement data. Register updates are still done via the CRUD views. This is a deliberate choice — enforcing automatic register mutation on execution adds complexity and risk for a workflow that is still being validated by Legal/HR.

9. Phase 3 — Simulation, Dividends & Workflow Enhancements

Phase 3 status (2026-04-14). Features 3.1 (dividends) and 3.3 (notifications) delivered. Feature 3.2 (simulation) deferred to the future backlog — no immediate business trigger; the CFO has not yet requested scenario modeling.

Theme. Dividend modeling, scenario simulation for planned structural changes, and workflow automation for movement approvals and notifications.

Why this phase now. - Phase 2 automated the high-volume work. Phase 3 addresses the analytical and workflow enhancements that make the cap table a decision-support tool, not just a register. - Dividend allocation is a once-a-year exercise but one where errors have legal consequences. Today it's a side spreadsheet; it should be computed from the authoritative register. - The CFO needs to preview the impact of planned structural changes (CSc acquisitions, new practice entities, dilution) before executing them. The register and cascade view from Phase 1 make this possible. - Notification routing reduces the manual coordination overhead as movement volume grows with acquisitions.

Foundational work delivered. - Dividend allocation rules per entity with CSb preference waterfall - Notification routing rules, French email templates, and Celery async delivery - Daily Celery beat task for stuck-movement reminders

Deferred. - Scenario modeling engine (Feature 3.2) — deferred to future backlog

Success metrics. - Annual dividend allocation is computed and reviewed in Aletheia, not in a side spreadsheet. MetDividendAllocationView computes from finance P&L and exports to Excel. - ~~CFO can preview the cascade and minority-interest impact of a planned CSc acquisition or new entity addition before executing it.~~ Deferred — Feature 3.2 moved to future backlog. - Movements stuck in pending_approval for more than N days trigger automatic reminders; no movements fall through the cracks. Met — daily Celery beat task sends reminders after 5 days.

Phase 3 features

# Feature Status Tables read/written Users Complexity Finance dep?
3.1 Dividend allocation computation Complete share_register, bond_register, holders, entities, gl_trial_balance_entry CFO, Finance L Yes (P&L data)
3.2 Cap table change simulation Deferred All cap table tables (read-only fork) CFO, Legal/HR L Yes (cascade)
3.3 Notification & workflow routing Complete cap_table_movements Legal/HR, CFO M No

3.1 — Dividend allocation computation

Description. Given a net result per entity and a distribution policy, compute per-holder dividend amounts: - Walk the FR cascade from entity-model.md §2 - At each entity, allocate net distributable result by FR% per holder - Handle the preference waterfall: CSb holders (typically the group entity) receive FR first; remaining goes to OS/CSa holders - Handle convertible instruments: CL/CB lenders receive interest (not dividends) unless converted - Export the allocation as an Excel schedule for Finance

The allocation is a read-only computation — it does not execute any payment. Finance uses the output to prepare the actual distribution.

Complexity. L. The preference waterfall and the CL/CB interest-vs-dividend distinction are the hard parts. The basic pro-rata allocation is straightforward. Finance dependency. Yes — needs entity net results from the finance P&L.

Implementation (2026-04-14). apps/captable/services/dividends.py (629 LOC): - get_entity_annual_net_results(fiscal_year) — queries GLTrialBalanceEntry for all FY periods, maps account codes to P&L report lines via GLMappingRule, sums per entity. FY periods follow the Oct–Sep fiscal year (e.g., FY2026 = Oct 2025 – Sep 2026). - _compute_entity_allocation(entity, net_result, distributable, as_of) — three-step algorithm: (1) bond interest for CL/CB holders from principal_amount × interest_rate, (2) CSb preferred holders receive their FR-weighted share first, (3) OS/CSa ordinary holders share the remainder. If distributable is insufficient, CSb is paid before OS/CSa. - compute_dividend_allocation(fiscal_year, overrides, as_of_date) — orchestrates all entities, supports per-entity distributable overrides, produces DividendAllocationResult with per-entity and per-holder summaries. - export_dividend_allocation_excel(result) — Excel export via OpenPyXL with entity-by-entity sheets + consolidated holder summary. - Views: DividendAllocationView (fiscal year selector, compute + display), DividendExportView (Excel download). Both gated by cap_table_view. - UI: dividend_allocation.html — entity-level breakdown (bond interest, preferred, ordinary allocations) + consolidated holder summary table. - 660 LOC of tests in test_dividends.py. Deviation: the plan described "walk the FR cascade from entity-model.md §2" — the implementation computes per-entity allocations independently from P&L data rather than walking the cascade tree. Each entity's net result comes directly from the GL trial balance, not from a cascaded computation. This is correct for dividend allocation (dividends are declared per entity, not cascaded through the group structure). Deviation: distributable overrides are user-configurable per entity in the UI, allowing the CFO to adjust distributable amounts (e.g., for legal reserves, carry-forward losses). This was not explicitly planned but is essential for real-world dividend allocation.

3.2 — Cap table change simulation

Description. A scenario tool that lets the CFO model "what if" changes before executing them: - "What if we acquire the CSc stake at THS?" → show new FR cascade, minority interest change - "What if we issue 100 new shares at CDA?" → show dilution impact on existing holders - "What if we acquire a new practice and add it as an operating entity under SDT?" → show the group structure with the new entity

Implementation: Fork the current ownership state into a transient scenario, apply hypothetical movements, recompute cascades and display deltas. Scenarios are not persisted beyond the session unless explicitly saved.

Complexity. L. The forking and delta computation are moderately complex; the UX for "what-if" scenarios is the harder design problem. Finance dependency. Yes — uses the cascade computation from Feature 1.6.

Deferred to future backlog (2026-04-14). No immediate business trigger — the CFO has not requested scenario modeling. The cascade view (Feature 1.6) provides the foundation; the simulation engine can be built on top when needed. Moved to §10 future backlog table.

3.3 — Notification & workflow routing

Description. When a movement is initiated: - Notify the configured approver(s) via email - Route to the right approver based on movement type and value (1-share practicing dentist → auto-approve or Legal/HR sign-off; significant minority → CFO) - Send reminders for movements stuck in pending_approval for more than N days - Notify Legal/HR when documents are ready for review - Notify the CFO when a movement is fully documented and archived

Uses existing Celery + SMTP infrastructure (Aletheia already sends emails via Celery).

Complexity. M. The routing rules and notification templates are the work; the infrastructure exists. Finance dependency. No.

Implementation (2026-04-14). apps/captable/services/notifications.py (254 LOC) + apps/captable/tasks.py (109 LOC): - get_recipients_for_transition(movement, to_status) — role-based routing: pending_approval → Finance Manager (approvers); approved → initiator + Legal/HR; rejected → initiator; executed → Legal/HR; documented/archived → Finance Manager (CFO role). - build_transition_email(movement, to_status, user, reason) — French plain-text email with movement details, status label, and action prompt. Subject line: [Aletheia] Mouvement {action} : {entity} — {type}. - build_stuck_reminder_email(movement, days_stuck) — reminder email for movements stuck in pending_approval. - find_stuck_movements(threshold_days=5) — queryset filter for movements in pending_approval older than threshold. - Celery tasks: send_workflow_notification (async email on each transition, called from transition_movement()), send_stuck_movement_reminders (daily beat task scanning for stuck movements). - Integration: transition_movement() in workflow.py calls send_workflow_notification.delay() after every successful transition. - Settings: CAPTABLE_NOTIFICATIONS_ENABLED flag (default True) and CAPTABLE_STUCK_THRESHOLD_DAYS (default 5) in config/settings/base.py. - 405 LOC of tests in test_notifications.py. Deviation: the plan described routing by movement type and value ("1-share practicing dentist → auto-approve or Legal/HR sign-off; significant minority → CFO"). The implementation routes all movements to Finance Manager for approval — no auto-approve for routine movements. SIGNIFICANT_MOVEMENT_TYPES is defined (transfer, capital change, conversion, entity transformation) but currently only used for classification, not differential routing. This is a deliberate simplification for launch; differential routing can be added when volume justifies it.

Phase 3 — what was deferred

  • Cap table change simulation (Feature 3.2): deferred to future backlog. No immediate business trigger; the CFO has not requested scenario modeling. The cascade view (Feature 1.6) and dividend computation (Feature 3.1) provide the analytical foundation; the simulation engine can be built on top when needed.
  • Differential approval routing: all movements go to Finance Manager for approval. Auto-approve for routine 1-share movements and CFO-specific routing for significant movements are deferred until movement volume justifies the added complexity.
  • Automatic register update on executed transition: carried forward from Phase 2 deferral. Register updates remain via CRUD views, not triggered by workflow execution.

10. Future backlog — deferred features

Features acknowledged and architecturally supported, but with no committed timeline. Any can be promoted to a phase when a business trigger arises.

Feature Original phase Why deferred Trigger to reconsider
Cap table change simulation (what-if scenarios) Phase 3 (3.2) No immediate business trigger; CFO has not requested scenario modeling. Cascade view (1.6) provides the foundation When CFO needs to preview impact of structural changes (CSc acquisitions, dilution, new entities) before executing them
Conversion event workflow (preview + execution) Phase 3 No business trigger on foreseeable horizon; data model already supports conversion per entity-model.md §3 When a CL/CB conversion is actually planned
Partner self-service portal Phase 3 Low partner count today (2-3 significant minorities) When partner count grows with acquisitions, or on explicit partner request
E-signature integration (DocuSign) Phase 3 Nice-to-have, not mandatory; DocuSign already in use externally for other purposes When document volume justifies the integration investment
Entity-scoped permissions Phase 3 Only needed for partner self-service When partner self-service is built
Automated PDF conversion Phase 2 Legal can "Save as PDF" manually; DocuSign accepts DOCX directly When/if e-signature integration is built, or if document volume makes manual PDF impractical
Workflow enforcement Phase 3 Track-only is sufficient while workflow is new; enforcement adds friction After 2-3 quarters of track-only operation, if compliance gaps emerge
Drop ShareRegisterEntry table Phase 1 (movement-first refactor, 2026-04-15) Table is deprecated — holdings are now derived from CapTableMovement replay via services/holdings.py. Model kept for migration history, admin set to read-only. No code path writes to it. After confirming no external queries (Metabase, scripts) depend on the table. Then: create a data migration to drop, remove model class, remove admin registration

11. Decisions needed — resolved

All 15 decisions from the initial draft have been resolved.

Data migration

  1. ~~Central entity migration strategy.~~ RESOLVED. Option (b): current state at launch, back-fill historical movements within 3 months. Finance needs current-state ownership percentages immediately; historical movements are less urgent. Collection from lawyers starts in Phase 1; doesn't block launch.

  2. ~~Historical data import depth.~~ RESOLVED. Option (b): full movement history where available. THS and VSM registries are complete — load in full. PDS snapshots converted to inferred movements. CDA, PDSh, SDV, VSMh get current state only.

  3. ~~Equity-only vs full-instrument cap table from day one.~~ RESOLVED. This is a requirement, not a decision. Convertible debt instruments (CL, CB) are first-class data model citizens from Phase 1. VSMh's 100% FR via CB and AST's 100% FR via CL are represented at launch. Non-negotiable for correct minority interest calculations.

Architecture

  1. ~~App location.~~ RESOLVED. Option (a): apps/captable/ — dedicated new app. Clean separation, own views/forms/templates/services. Cross-app FKs to apps/entities/ are standard Django practice.

  2. ~~Document generation technology.~~ RESOLVED. Option (a): docxtpl (Jinja2 for DOCX). Legal authors templates in Word with placeholders; engineers build the data context. Legal must own document formatting — these are legally binding documents.

  3. ~~PDF conversion.~~ RESOLVED (deferred). DOCX-only generation for all committed phases. Legal produces PDF manually ("Save as PDF") when needed. Automated PDF conversion reconsidered only when/if e-signature integration is built. DocuSign accepts DOCX directly, so this does not block future e-signature.

Workflow and governance

  1. ~~Aletheia as source of truth vs secondary copy.~~ RESOLVED. Option (b): parallel operation. CFO decides when to retire the spreadsheet — this is a judgment call, not a system requirement.

  2. ~~E-signature integration.~~ RESOLVED (deferred to future backlog). DocuSign is the provider (already in use at the group for other purposes). Not committed to any phase — nice-to-have when document volume justifies the investment.

  3. ~~Conversion events — design now or defer?~~ RESOLVED (deferred to future backlog). Data model already supports conversion per entity-model.md §3. No business trigger on the foreseeable horizon. Build the preview/execution workflow when a real conversion is planned, not speculatively.

  4. ~~Workflow automation boundary.~~ RESOLVED. Option (a): track only. Aletheia records movement status; humans enforce the process. Tightening to enforcement is a future-backlog consideration once the workflow is proven.

  5. ~~Partner self-service scope.~~ RESOLVED (deferred to future backlog). Low urgency today (2-3 significant minority partners). Build when partner count grows enough that CFO request load justifies the investment.

Data sensitivity and compliance

  1. ~~Minimum viable permission model.~~ RESOLVED. Option (a): role-based only for all committed phases. CFO, Legal/HR Manager, Admin see everything; everyone else sees nothing. Entity-scoped permissions deferred to future backlog alongside partner self-service.

  2. ~~Audit retention and GDPR.~~ RESOLVED. Indefinite retention for cap table records. Cap table records are legal/corporate records under French commercial law (Code de commerce), exempt from GDPR right-to-erasure. No formal Legal review needed — this is understood internally.

  3. ~~Interaction with Finance roadmap — coordination.~~ RESOLVED (pre-resolved in entity-model.md §7 Q3). Day one. Both roadmaps ship Phase 1 together. Individual holders are populated immediately; finance views aggregate them per entity at query time.

  4. ~~Integration back to Pennylane.~~ RESOLVED. Manual. Finance team handles the Pennylane side of any cap table event. No automation planned.


12. Appendix A: Entity model reference

The full entity model, ownership tree, consolidation methodology, and data model are in docs/entity-model.md. Key sections for cap table work:

  • §1 Share class vocabulary — OS, CSa/b/c, CL, CB definitions; CVR vs FR independence
  • §2 Legal ownership tree — the 11-entity structure
  • §3 Consolidation methodology — minority interest from FR side; CL/CB economic attribution; conversion events
  • §4 Data modelentities, ownership_links (derived/cached), holders, ownership_events schemas
  • §7 Resolved questions — individual holders from day one, CSc acquisition handling, fiscal year alignment

13. Appendix B: Existing cap table artifacts reference

All reference material is in temp/pennylane_reusable/reference/. The catalog is in reference/README.md.

Key files for cap table work

Folder Key files What to extract
captables/ THS - Table de capitalisation 30 03 2026.xlsx Full cap table with dual KDV/DF columns, multiple share classes
THS - Liste des actionnaires 30 03 2026.xlsx Shareholder list with period movement columns
THS - Registre de mouvement de titres 30 03 2026.xlsx Complete movement registry — the migration source
VSM - Tables de capitalisation_v 09 03 26.xlsx Cap table with OS structure
VSM - Registre de mouvement de titres ... .xlsx Movement registry with complex events (splits, conversions)
CDA, PDS, PDSh, SDV, VSMh Simpler cap tables — migration inputs
documentation dentists entries/ PDS 260410 BASTIEN Armeline/ Complete 5-doc DOCX entry pack — template source
THS 260330 Goncalves Francisco/ 4-doc PDF entry pack
VSM 260218 DUPRAT Florence/ PV d'AG variant (vs DUA)
documentation dentists exits/ PDS 1022 Moussaoui Hassan/ Complete 3-doc exit pack — template source

What is NOT in the reference folder

  • Central entity cap tables (CHC, SUI, AST, SDT) — at the lawyers
  • Convertible instrument documentation (CL/CB contracts, prêt participatif agreements) — at the lawyers
  • Shareholder agreements — at the lawyers
  • Historical PV d'AG / ODM archives — presumed at the lawyers or in entity-level physical files

These gaps are addressed by Phase 1 Feature 1.3 (central entity migration) and should be collected as part of that workstream.


End of roadmap v1. Feedback on this document goes to the cap table roadmap owner. Any change that touches the entity, ownership, or consolidation model must be reflected in docs/entity-model.md first. Changes to document generation patterns or Legal/HR workflows should be reviewed by Legal before implementation.