Skip to content

SA Contact Model

Purpose

This document defines how SA governance applies to Odoo contact records.

It is a second-layer implementation note under the broader SA model. It should be read together with:

This document is intentionally narrower than those sources. Its job is to state what contact governance means for res.partner, what fields are needed, what should remain native Odoo, and what audit rules must exist.

Scope

This note covers:

  • customer and person records anchored in res.partner
  • PA-driven creation and management of SA-governed contacts
  • attribution, visibility, reassignment, and audit expectations

This note does not redefine:

  • the SA hierarchy model
  • the membership model
  • asset governance
  • Odoo accounting, CRM, or stock behavior

Governing References

The current contact model inherits these already-established decisions from the repo:

  1. res.partner remains the identity anchor for both people and organizations.
  2. SA authority lives on membership, not on the partner record itself.
  3. PA establishes the current {actor, sa} context before CRUD execution.
  4. {actor, sa} is both attribution data and an operational selector for later reads and updates.
  5. If actor is set, that actor must be a valid member of the stamped SA.

Those rules come primarily from:

Contact Modeling Position

Keep res.partner Native

The contact model should stay conservative.

Do not create a separate SA-native contact wrapper for v0.

Use native res.partner as the system-of-record anchor for:

  • organizations
  • people
  • addresses
  • contact methods
  • standard Odoo CRM and commercial relationships

This follows the same design discipline established elsewhere in the repo:

  • native Odoo remains the transaction and identity substrate
  • SA adds governance boundaries around native objects

What SA Adds

For contacts, SA adds governance metadata and rules, not a replacement contact model.

The minimum SA extension to contact governance is:

  • one SA boundary field
  • one actor assignment field
  • audit fields sufficient to reconstruct governance changes

Proposed Contact Governance Fields

For SA-governed res.partner records, the current implementation direction should be:

Field Type Purpose Guidance
x_serviced_account_id Many2one(ov.serviced_account) SA governance boundary for the contact Nullable for non-PA/native records; required for PA-governed contact records
x_actor_partner_id Many2one(res.partner) Current assigned actor inside the SA Nullable; if set, must resolve to a valid active membership in the stamped SA
x_pa_created Boolean Distinguish PA-governed creation path from ordinary Odoo creation Optional but strongly recommended
x_last_governance_change_at Datetime Last change to SA/actor governance fields Recommended
x_last_governance_change_by Many2one(res.partner) Human actor responsible for latest governance change Recommended

Naming note:

  • The exact field names are still implementation-level choices.
  • The important part is semantic consistency with the repo’s established {actor, sa} model.

Semantics of the Contact Stamp

The governing pair for contacts is:

  • {actor, sa}

Mapped for res.partner as:

  • actor -> x_actor_partner_id
  • sa -> x_serviced_account_id

Interpretation:

  • {actor = null, sa = X} means the contact belongs to SA X and is shared or unassigned inside that SA
  • {actor = A, sa = X} means the contact belongs to SA X and is currently assigned to actor A

This pair describes current governance state. It is not sufficient by itself as full history.

Contact Types and How Governance Applies

Organization Contacts

For organization records in res.partner:

  • the contact may represent a customer company, branch, partner organization, or account-level node
  • the stamped SA identifies which serviced account governs that organization record in PA workflows

This does not mean the organization itself becomes the SA object.

The SA object remains ov.serviced_account. The partner record is still the contact/identity record.

Person Contacts

For person records in res.partner:

  • the contact may be a customer-side user, operator, buyer, account contact, or internal operational person
  • the stamped SA indicates in which governance boundary the person contact is currently being managed

Important distinction:

  • a person may exist in res.partner once
  • that same person may participate in multiple governed contexts through membership or commercial relationships
  • therefore SA governance on a contact record must be treated as application governance for the record, not a universal statement about the person’s entire identity

Creation Paths

PA-Created Contacts

When PA creates a new governed contact:

  1. PA must already have an active {actor, sa} context.
  2. The created res.partner record must be stamped with that SA.
  3. The created res.partner record should normally also be stamped with the creating actor.
  4. The creation event should be written to a governance audit trail.

Default rule:

  • PA-created customer/person contacts should start as {current_actor, current_sa}

Exception:

  • some apps may intentionally create shared contacts as {actor = null, sa = current_sa}

That exception must be explicit in app policy, not accidental.

Native Odoo or Legacy Contacts

Contacts created outside PA may have no SA stamp.

That is acceptable.

The model should not require backfilling every historical res.partner row before rollout.

Governance rule:

  • non-PA-native contacts may remain unstamped until they enter an SA-governed workflow

Visibility Rules for Contacts

Contact visibility follows the same pattern already established for other SA-governed records.

Base Rule

A contact is visible in PA only when:

  • contact.x_serviced_account_id == current_sa

Actor Rule

Inside the SA boundary:

  • sa is the inclusive selector
  • actor is the exclusive selector

Supported Policy Shapes

The current contact implementation should support these policy modes:

Policy Meaning
assigned_only actor sees only contacts where x_actor_partner_id == current_actor
assigned_plus_unassigned actor sees contacts assigned to self or unassigned contacts within the SA
sa_wide actor sees all contacts in the current SA

Suggested initial hierarchy direction:

  • an ordinary member should use assigned_only or assigned_plus_unassigned depending on app policy
  • a member with subordinates may get broader supervision/reporting inside the current SA through hierarchy-based team roll-up
  • the current sa_manager may additionally get descendant-SA reach following the broader SA hierarchy rule already defined in the main SA model

Rationale:

  • contact governance should follow the same hierarchy semantics as the broader SA model
  • this avoids inventing one visibility model for contacts and another for the rest of the SA-governed objects
  • hierarchy remains more reliable than semantic labels because the reporting path is derived from explicit structure rather than interpretation of titles

Separation From Odoo Native Security

This model does not eliminate Odoo ACLs and record rules.

It changes where business governance is decided.

The expected split is:

Layer Responsibility
Odoo ACL / record-rule layer Enforce technical read/write boundaries for the proxy execution strategy
PA app layer Establish current context, decide app policy, and prevent out-of-scope operations before execution
SA membership logic Determine whether the current actor is allowed to operate in the current SA

Practical rule:

  • do not rely on plain Odoo salesperson ownership as the contact-governance model
  • do not rely on company_id alone to represent SA scope

Integrity Rules

The following rules should be treated as non-negotiable.

Rule 1: Membership Integrity

If x_actor_partner_id is not null, that person must hold a valid active membership in x_serviced_account_id.

Broken state example:

  • x_actor_partner_id = A
  • x_serviced_account_id = SA_X
  • actor A has no valid membership in SA_X

That is an integrity failure.

Rule 2: Normalization on Membership Removal

If membership removal invalidates the current stamp:

  • set x_actor_partner_id = null

Do not leave a broken {actor, sa} pair on the contact.

Rule 3: Explicit Reassignment

If a contact is moved from actor A to actor B inside the same SA:

  • the operation must validate membership for B
  • the reassignment must be auditable

Rule 4: SA Transfer

If a contact is moved from SA X to SA Y:

  • the operation must be explicit
  • actor assignment must be revalidated
  • if the existing actor is not valid in Y, normalize actor to null or assign a valid actor in Y

This is not a casual field edit. It is a governance transfer.

Audit Model

The earlier draft correctly identified that the live {actor, sa} stamp is not enough for history.

That now becomes an implementation requirement.

Minimum Audit Requirement

Every change to contact governance should produce a durable audit event containing at least:

  • contact record id
  • previous sa
  • new sa
  • previous actor
  • new actor
  • operation type
  • effective timestamp
  • responsible human actor
  • execution channel such as PA or administrative Odoo action

Preferred Implementation Direction

The cleanest direction is a separate audit model, for example:

  • ov.governance_audit_log

Minimum event classes:

  • contact_created
  • contact_assignment_changed
  • contact_unassigned
  • contact_sa_transferred
  • membership_normalization

Why separate audit storage is preferred:

  • the live fields on res.partner should represent current state
  • history is append-only and should not depend on reconstructing chatter or generic write logs
  • governance events will likely need cross-object analysis later, not only contact-specific review

Operational Guidance for v0

If implementation starts now, the contact prototype should prove these behaviors:

  1. A PA-created contact can be stamped with {actor, sa}.
  2. Contacts are filterable by current SA first, then actor policy.
  3. Reassignment to another actor validates membership.
  4. Membership removal normalizes invalid actor assignments to null.
  5. Governance changes create append-only audit events.
  6. Non-PA legacy contacts can coexist without immediate forced migration.

Risks That Remain Open

These items are still open design questions, but they no longer block this document from being implementation-guiding:

  • exact field names and whether the governance stamp should be provided by a reusable mixin
  • whether supervisory contact views belong fully in PA or partly in Odoo back office
  • the exact hierarchy-based authority boundary for contact reassignment and SA transfer
  • whether some contact classes should be immutable across SA transfer without approval flow

The current recommended position for contacts is:

  • keep res.partner as the native contact model
  • add a conservative SA governance stamp to SA-managed contacts
  • treat {actor, sa} as current-state governance metadata
  • require a separate audit log for historical truth
  • derive permission from membership and PA context, not from partner ownership alone

That position is consistent with the established SA/PA governance documents already in this repo and keeps the contact model aligned with the broader design principle:

  • governance is added around native Odoo objects, not by replacing them