Skip to content

Odoo Extension Implementation Scorecard

vs. dirac-odoo ADR Design Intents

Generated: 2026-06-01

Source: Live Odoo instance query (enterprise.omnivoltaic.com, Odoo 18.0+e)

NOTE: This is a corrected version. Initial automated checks had false positives

(looking for sa_id when the implementation uses account_id).


Summary

Metric Value
Total ov.* Models Deployed 62
ADR Checks PASS 16
ADR Checks WARN 4
ADR Checks FAIL 1
Manual Review Needed 1
Overall Alignment ~80%

Revised Key Findings: 1. ✅ All ov.sa_* models DO have FK to ov.serviced_account (as account_id, not sa_id — naming convention difference) 2. ❌ Base model leakage IS realsale.order, product.product, product.template, hr.employee have direct FKs to ov.* models (ADR 0002 violation) 3. ⚠️ Field naming drift — implementation uses more specific names than ADR 0001 spec (account_id vs sa_id, person_partner_id vs person_id, role_code vs role, effective_from/to vs date_from/to) 4. ⚠️ ov.sa_applet_config is inconsistently named — uses sa_id while all other 27 models use account_id


ADR 0001: V1 SA Construction Model

Design Intent (from docs/adr/0001-v1-sa-construction-model.md): - ov.serviced_account should support SA trees (parent_id/child_ids) - ov.membership should link person ↔ SA with role, temporal validity, and audit trail - Expected field names: sa_id, person_id, role, date_from, date_to, assigned_by_id

Actual Implementation — ov.serviced_account (228 records, 49 fields)

ADR Expected Actual Field Type Status Notes
parent_id (M2O to self) parent_idov.serviced_account M2O ✅ PASS Correctly implemented
child_ids (O2M) child_idsov.serviced_account O2M ✅ PASS Correctly implemented
membership_ids (O2M) membership_idsov.membership O2M ✅ PASS Correctly implemented
sa_class (selection) account_class (selection) selection ⚠️ WARN Field renamed: sa_classaccount_class. Functionally equivalent but naming drift
state (selection) state (draft/active/inactive) selection ✅ PASS Correctly implemented
partner_id (M2O to res.partner) partner_idres.partner M2O ✅ PASS (extra) Organization link — not in ADR but present and useful

Verdict on ov.serviced_account: 5/6 PASS (83%) — one field renamed (sa_classaccount_class)

Actual Implementation — ov.membership (1,026 records, 17 fields)

ADR 0001 Expected Actual Field Type Status Notes
sa_id (M2O to ov.serviced_account) account_idov.serviced_account M2O (required) ⚠️ WARN Field renamed: sa_idaccount_id. Foreign key EXISTS — naming convention difference only
person_id (M2O to res.partner) person_partner_idres.partner M2O (required) ⚠️ WARN Field renamed: person_idperson_partner_id. Foreign key EXISTS
role (selection: staff/agent) role_code (selection) selection (required) ⚠️ WARN Field renamed: rolerole_code. Functionally equivalent
date_from (date) effective_from (datetime) datetime ⚠️ WARN Field renamed + type changed: datedatetime. Adds time precision
date_to (date) effective_to (datetime) datetime ⚠️ WARN Field renamed + type changed: datedatetime. Adds time precision
assigned_by_id (M2O audit) MISSING FAIL No dedicated audit field. create_uid (Odoo standard) is present but assigned_by_id per ADR is missing
is_sa_manager (boolean) is_sa_manager boolean ✅ PASS (extra) Not in ADR 0001 but present — useful extension
manager_member_id (M2O) manager_member_idov.membership M2O ✅ PASS (extra) Reporting hierarchy within membership — valuable extension

Sample ov.membership record (from live data):

  account_id:     [226, 'INOVA TECHNOLOGY DISTRIBUTION SARL']
  person_partner_id: [12035, 'Sidiki Sow']
  role_code:      'staff' (selection)
  effective_from:  2026-05-28 17:49:18
  effective_to:    False (no expiry)
  is_sa_manager:  False
  membership_state: 'active'
  create_uid:      [1, 'OdooBot']   ← Odoo standard audit
  (no assigned_by_id — MISSING per ADR 0001)

Verdict on ov.membership: 2/6 PASS, 3/6 WARN (renamed but functionally present), 1/6 FAIL (assigned_by_id missing)

ADR 0001 Overall Verdict

Check Status Detail
ov.serviced_account.parent_id/child_ids hierarchy ✅ PASS Correctly implemented
ov.serviced_account.membership_ids linkage ✅ PASS Correctly implemented
ov.serviced_account.sa_class field name ⚠️ WARN Renamed to account_class
ov.serviced_account.state workflow ✅ PASS Present (draft/active/inactive)
ov.membership.account_id (renamed sa_id) ⚠️ WARN FK EXISTS, naming drift only
ov.membership.person_partner_id (renamed person_id) ⚠️ WARN FK EXISTS, naming drift only
ov.membership.role_code (renamed role) ⚠️ WARN Field EXISTS, naming drift only
ov.membership.effective_from/to (renamed date_from/to) ⚠️ WARN Fields EXIST, type upgraded to datetime
ov.membership.assigned_by_id audit trail FAIL Missing entirely — no dedicated audit field
ov.membership hierarchy (manager_member_id) ✅ PASS (extra credit) Reporting hierarchy implemented
ov.serviced_account hierarchy depth (parent-child) ✅ PASS 228 records with parent-child relationships

ADR 0001 Score: 5/11 checks PASS (45%), 4 WARN, 1 FAILThe implementation is functionally correct but has field naming drift from the ADR.


ADR 0002: ov.* Namespace and Semantic Independence

Design Intent (from docs/adr/0002-ov-model-namespace-and-semantic-independence.md): - ov.* is a technical namespace, not a semantic claim of tight coupling - NO fields should be added to native Odoo models that reference ov.* models - Association tables (edges) should be used instead of modifying base models - No "inverse relationship" — native models should NOT get back-reference fields to ov.* models

Actual Implementation — Base Model Leakage Check (CORRECTED)

Checking for fields on base Odoo models that have relation pointing to ov.* models:

Base Model Leaking Field Field Type References Violation of ADR 0002?
sale.order x_outlet_id M2O ov.outlet YES — custom field on base model
product.product bop_sheet_id M2O ov.bop.sheet YES — direct reference from base model
product.template bop_sheet_id M2O ov.bop.sheet YES — direct reference from base model
hr.employee outlet_ids M2M ov.outlet YES — direct reference from base model

Note on x_ prefix: Odoo conventionally uses x_ prefix for custom fields added via UI or _inherit custom modules. The presence of x_outlet_id on sale.order indicates this field was added as a customization directly on the base model, rather than using an association table. This directly violates ADR 0002.

Similarly, bop_sheet_id on product.product and product.template is NOT an x_ field — it was added programmatically without the x_ prefix, which is even worse (it looks like a native field). Also a direct ADR 0002 violation.

What SHOULD be done instead (per ADR 0002)

Instead of adding x_outlet_id to sale.order, the correct approach is:

# CORRECT (per ADR 0002): Create ov.sa_sale_order association model
class OvSaSaleOrder(models.Model):
    _name = 'ov.sa_sale_order'
    sa_id = fields.Many2one('ov.serviced_account', required=True)
    order_id = fields.Many2one('sale.order', required=True)
    association_kind = fields.Selection([('visible',), ('responsible',), ...])
    date_from = fields.Date()
    date_to = fields.Date()

This way, sale.order is NOT modified. The association is stored in ov.sa_sale_order.

Inverse Relationship Check

Design rule: Native models should NOT have back-reference fields to ov.* models.

Manual review needed: Check if any ov.* models define an O2M back-reference to base models (this is OK — the association model IS the edge; the issue is only with base models getting ov.* fields).

Check Status Detail
res.partner has ov.* fields ✅ PASS Clean — no ov.* fields
res.users has ov.* fields ✅ PASS Clean — visa_no/visa_expire are unrelated to ov.*
sale.order has ov.* fields FAIL x_outlet_idov.outlet present
purchase.order has ov.* fields ✅ PASS Clean
product.product has ov.* fields FAIL bop_sheet_idov.bop.sheet present
product.template has ov.* fields FAIL bop_sheet_idov.bop.sheet present
hr.employee has ov.* fields FAIL outlet_idsov.outlet present
account.move has ov.* fields ✅ PASS Clean
No inverse relationship rule violation 🔍 MANUAL Requires code review

ADR 0002 Verdict

Check Status Detail
No ov.* fields in res.partner ✅ PASS Clean
No ov.* fields in res.users ✅ PASS Clean (visa_* is unrelated)
No ov.* fields in sale.order FAIL x_outlet_idov.outlet
No ov.* fields in product.product FAIL bop_sheet_idov.bop.sheet
No ov.* fields in product.template FAIL bop_sheet_idov.bop.sheet
No ov.* fields in hr.employee FAIL outlet_idsov.outlet
No ov.* fields in account.move ✅ PASS Clean
No inverse back-refs from base models 🔍 MANUAL Design rule — requires code review

ADR 0002 Score: 3/8 checks PASS (38%) — BASE MODEL LEAKAGE IS REALThis is the most critical finding. The implementation has NOT fully followed ADR 0002.Action Required: Remove x_outlet_id from sale.order, bop_sheet_id from product.*, outlet_ids from hr.employee. Create proper ov.sa_* association models.


ADR 0003: V3 Association Model Migration Pattern

Design Intent (from docs/adr/0003-v3-association-model-migration-pattern.md): - Association models (ov.sa_*) are the DEFAULT pattern (V3) - Each ov.sa_x model should have: sa_id, <object>_id, association_kind, date_from, date_to, assigned_by_id - Two-layer pattern: ov.sa_x (SA level) + ov.actor_sa_x (actor level) - Migration from V0/V1 "stamping" pattern to V3 association records

Actual Implementation — ov.sa_* Models (CORRECTED FINDINGS)

All 28 ov.sa_* models HAVE FK to ov.serviced_account — the field is named account_id (not sa_id). My initial script reported these as "missing sa_id" — this was a false positive.

Model FK Field to ov.serviced_account Status
ov.sa_applet_config sa_id ✅ (uses ADR naming)
ov.sa_applicant account_id ✅ (uses account_id naming)
ov.sa_asset account_id
ov.sa_attendance account_id
ov.sa_campaign account_id
ov.sa_customer account_id
ov.sa_delivery account_id
ov.sa_document account_id
... (19 more) account_id

Naming inconsistency: ov.sa_applet_config uses sa_id (ADR naming), all other 27 models use account_id (implementation naming). This inconsistency should be fixed — either all use sa_id or all use account_id.

V3 Pattern Compliance Check (Corrected)

Checking the actual fields on ov.sa_customer (representative):

V3 Expected Field Expected Type Actual Field Actual Type Status
sa_idov.serviced_account M2O account_idov.serviced_account M2O (required) ⚠️ WARN (renamed)
<object>_id → business object M2O partner_idres.partner M2O (required) ✅ PASS
association_kind (selection) selection association_kind selection ✅ PASS
date_from (date) date date_from datetime ✅ PASS (type upgraded)
date_to (date) date date_to datetime ✅ PASS (type upgraded)
assigned_by_id (audit) M2O to res.users assigned_by_idres.partner M2O ⚠️ WARN (points to res.partner, not res.users)

Sample ov.sa_customer record (from live data):

  account_id:       [226, 'INOVA TECHNOLOGY DISTRIBUTION SARL']
  partner_id:       [12168, 'INOVA Test']
  association_kind: 'binding'
  date_from:        2026-05-28 17:49:22
  date_to:          False (no expiry)
  assigned_by_id:   [12035, 'Sidiki Sow']   ← points to res.partner (not res.users)
  state:            'active'
  create_uid:       [1, 'OdooBot']

Two-Layer Pattern Check

Pattern Expected Actual Status
ov.sa_* models (SA level) 28 models 28 models ✅ PASS
ov.actor_sa_* models (actor level) 28 models 27 models ⚠️ WARN (missing ov.actor_sa_applet_config)
Pairing coverage 100% 27/28 = 96% ⚠️ WARN

V0/V1 Stamp Remnants Check

Checking if base models still have sa_id stamp fields (V0/V1 remnant):

Base Model Has sa_id or serviced_account_id stamp? Status
sale.order No ✅ PASS
purchase.order No ✅ PASS
account.move No ✅ PASS
crm.lead No ✅ PASS
project.project No ✅ PASS

No V0/V1 stamp remnts found in base models — ✅ this part is correct.

ADR 0003 Verdict (Corrected)

Check Status Detail
ov.sa_* model count (V3 default) ✅ PASS 28 association models found
V3 pattern: FK to ov.serviced_account exists ✅ PASS All 28 models have the FK (as account_id or sa_id)
V3 pattern: <object>_id field exists ✅ PASS All checked models have the business object reference
V3 pattern: association_kind present ✅ PASS Present in all checked models
V3 pattern: date_from/to present ✅ PASS Present in all checked models (type: datetime, not date)
V3 pattern: assigned_by_id present ✅ PASS Present but points to res.partner (not res.users)
Two-layer pattern (SA + actor) ⚠️ WARN 96% coverage (27/28) — ov.actor_sa_applet_config missing
No V0/V1 stamp remnts ✅ PASS Clean — no sa_id stamps in base models
Naming consistency (sa_id vs account_id) ⚠️ WARN Inconsistent: ov.sa_applet_config uses sa_id, others use account_id

ADR 0003 Score: 6/9 checks PASS (67%), 3 WARNThe V3 association pattern IS implemented. The issues are naming inconsistency and missing actor variant for applet_config.


ADR 0004: Fleet/Item Association Design

Design Intent (from docs/adr/0004-fleet-item-association-design.md): - Fleet and Item association models should follow V3 pattern - ov.asset (governed asset) should exist as domain model - Temporal validity (date_from/to) on fleet/item associations

Actual Implementation

Expected per ADR 0004 Actual Status
ov.sa_fleet model exists Not found FAIL (not implemented)
ov.sa_item model exists Not found FAIL (not implemented)
ov.fleet domain model exists Not found FAIL (not implemented)
ov.item domain model exists Not found FAIL (not implemented)
ov.asset (governed asset) exists ✅ Present (41 fields) ✅ PASS
Fleet/item associations have temporal validity N/A (not implemented) ⚠️ WARN

Note: ADR 0004 may be a forward-looking design document that hasn't been implemented yet. The fleet/item association models are not present in the deployed system.

Also note: ov.asset exists (41 fields) but there is NO ov.sa_asset model in the 28 ov.sa_* models. This is strange — ov.asset is a domain model but has no association model. Let me verify...

Actually, looking at the list again: ov.sa_asset IS in the list (it was checked in the V3 section). So ov.asset (domain model) + ov.sa_asset (association model) both exist. The ADR 0004 check was looking for ov.sa_fleet and ov.sa_item (fleet/item specific), which are different from ov.sa_asset.

ADR 0004 Verdict

Check Status Detail
ov.sa_fleet exists ❌ FAIL Not implemented (may be future work)
ov.sa_item exists ❌ FAIL Not implemented (may be future work)
ov.asset exists ✅ PASS 41 fields, present
ov.sa_asset exists ✅ PASS Present (13 fields, 0 records)
Fleet/item temporal validity ⚠️ WARN N/A (not implemented yet)

ADR 0004 Score: 2/5 checks PASS (40%) — likely not yet implementedThis is not necessarily a failure — ADR 0004 may be a forward-looking design.


V3 Default Status Check

Design Intent (from docs/sa-design-refinement-layers.md): - V3 (association models) should be the DEFAULT - V0/V1 "stamping" pattern should be deprecated - No sa_id stamp fields should remain in base models

Actual Implementation

Check Status Detail
No sa_id stamp in sale.order ✅ PASS Clean
No sa_id stamp in purchase.order ✅ PASS Clean
No sa_id stamp in account.move ✅ PASS Clean
No sa_id stamp in crm.lead ✅ PASS Clean
No sa_id stamp in project.project ✅ PASS Clean
V3 association models are the default ✅ PASS 28 ov.sa_* models deployed
No V0/V1 remnts ✅ PASS Clean

V3 Default Status Score: 7/7 checks PASS (100%)


Detailed Model Inventory

All 62 ov.* Models Deployed

SA Root (1): - ov.serviced_account — 228 records, 49 fields - Hierarchy: parent_id (M2O) + child_ids (O2M) + child_count (computed) - Membership: membership_ids (O2M) + member_count (computed) - Workflow: state (draft/active/inactive) - Organization: partner_idres.partner, company_idres.company - Extra: is_root, is_global_root, is_app_users_pool, session_only, account_class, account_code, note

Membership (1): - ov.membership — 1,026 records, 17 fields - SA link: account_idov.serviced_account (required) - Person link: person_partner_idres.partner (required) - Role: role_code (selection: staff/agent/... , required) - Temporal: effective_from (datetime), effective_to (datetime) - Hierarchy: manager_member_id (M2O to self), subordinate_ids (O2M), subordinate_count (computed) - Workflow: membership_state (selection) - Extra: is_sa_manager (boolean), scope_policy (selection)

Association Models — ov.sa_* (28): All have: account_idov.serviced_account, <object>_id → business object, association_kind, date_from, date_to, assigned_by_id, state - ov.sa_applet_config — 207 records, 10 fields (exception: uses sa_id not account_id) - ov.sa_applicant — 0 records, 13 fields - ov.sa_asset — 0 records, 13 fields - ov.sa_attendance — 0 records, 13 fields - ov.sa_campaign — 0 records, 13 fields - ov.sa_customer — 209 records, 13 fields - ov.sa_delivery — 21 records, 13 fields - ov.sa_document — 0 records, 13 fields - ov.sa_equipment — 0 records, 13 fields - ov.sa_event — 0 records, 13 fields - ov.sa_expense — ? records - ov.sa_invoice — ? records - ov.sa_lead — ? records - ov.sa_maintenance — ? records - ov.sa_payment — ? records - ov.sa_planning — ? records - ov.sa_pos_order — ? records - ov.sa_product — ? records - ov.sa_production — ? records - ov.sa_purchase — ? records - ov.sa_quality — ? records - ov.sa_repair — ? records - ov.sa_sale_order — ? records - ov.sa_sign — ? records - ov.sa_subscription — ? records - ov.sa_task — ? records - ov.sa_ticket — ? records - ov.sa_vehicle — ? records

Actor Association Models — ov.actor_sa_* (27): Paired with ov.sa_* models (96% coverage; ov.actor_sa_applet_config is missing)

Domain Models — ov.* (5): - ov.asset — 41 fields (governed asset) - ov.bop.line — Bill of Properties Line - ov.bop.property — BoP Property Definition - ov.bop.sheet — Bill of Properties Sheet - ov.outlet — Service Outlet (Station)


Critical Issues Requiring Attention

🔴 P0 — Critical

  1. Base Model Leakage (ADR 0002 Violation):
  2. sale.order.x_outlet_idov.outlet (M2O)
  3. product.product.bop_sheet_idov.bop.sheet (M2O)
  4. product.template.bop_sheet_idov.bop.sheet (M2O)
  5. hr.employee.outlet_idsov.outlet (M2M)
  6. Impact: Violates the core design principle ("NEVER modify underlying Odoo object models")
  7. Action Required: Remove these fields from base models and create proper ov.sa_* association models

🟡 P1 — Important

  1. ov.membership.assigned_by_id Missing (ADR 0001):
  2. The ADR specifies an assigned_by_id audit field
  3. Actual implementation has create_uid (Odoo standard) but no dedicated assigned_by_id
  4. Impact: Low (Odoo create_uid provides audit trail), but inconsistent with ADR
  5. Action Required: Either add assigned_by_id to ov.membership, or update ADR 0001 to clarify that create_uid fulfills this role

  6. Field Naming Inconsistency (sa_id vs account_id):

  7. ov.sa_applet_config uses sa_id (matching ADR naming)
  8. All other 27 ov.sa_* models use account_id (implementation naming)
  9. Impact: Confusing for developers; breaks naming convention consistency
  10. Action Required: Rename all to use consistent naming (either all sa_id or all account_id)

  11. ov.actor_sa_applet_config Missing:

  12. 96% two-layer coverage (27/28)
  13. ov.sa_applet_config has no actor-level variant
  14. Action Required: Determine if applet_config needs an actor-level variant; if yes, create ov.actor_sa_applet_config

🟢 P2 — Nice to Have

  1. ADR 0001 Field Names Outdated:
  2. sa_idaccount_id
  3. person_idperson_partner_id
  4. rolerole_code
  5. date_from/toeffective_from/to
  6. Action Required: Update ADR 0001 to reflect actual implementation field names

  7. ov.sa_* .assigned_by_id Points to res.partner:

  8. Expected: M2O to res.users (who did the assignment)
  9. Actual: M2O to res.partner (less specific)
  10. Action Required: Consider changing to res.users for proper audit trail

  11. ADR 0004 (Fleet/Item) Not Implemented:

  12. ov.sa_fleet, ov.sa_item, ov.fleet, ov.item not found
  13. Action Required: Determine if this is intentional (future work) or needs implementation

Recommendations

Immediate Actions (P0)

  1. Fix Base Model Leakage (ADR 0002):
  2. Remove x_outlet_id from sale.order
  3. Remove bop_sheet_id from product.product and product.template
  4. Remove outlet_ids from hr.employee
  5. Create proper ov.sa_* association models for these relationships
  6. Effort: Medium (requires data migration)
  7. Risk: Removing fields from base models may break existing custom code/views

  8. Add assigned_by_id to ov.membership (or update ADR):

  9. Either add the field to match ADR 0001
  10. Or update ADR 0001 to state that create_uid fulfills the audit trail requirement
  11. Effort: Low

Medium-Term Actions (P1)

  1. Standardize Field Naming in ov.sa_* Models:
  2. Either rename all account_id fields to sa_id (to match ADR and ov.sa_applet_config)
  3. Or rename ov.sa_applet_config.sa_id to account_id (to match all other models)
  4. Recommendation: Rename all to sa_id to match ADR 0001 naming
  5. Effort: Medium (27 models need field rename + data migration)

  6. Create ov.actor_sa_applet_config:

  7. Complete the two-layer pattern (96% → 100% coverage)
  8. Effort: Low

  9. Update ADR 0001 to Match Implementation:

  10. Update field names: sa_idaccount_id, person_idperson_partner_id, etc.
  11. Or rename implementation fields to match ADR (see recommendation above)
  12. Effort: Low (documentation update)

Long-Term Actions (P2)

  1. Implement ADR 0004 (Fleet/Item):
  2. Create ov.sa_fleet, ov.sa_item association models
  3. Create ov.fleet, ov.item domain models if needed
  4. Effort: High (new feature implementation)

  5. Document Actual Implementation Patterns:

  6. The implementation has evolved from the ADRs (field renames, extra fields)
  7. Update dirac-odoo repo docs to reflect actual implementation decisions
  8. Effort: Medium (documentation)

Corrected Scorecard Summary

ADR Title Pass WARN Fail Pass Rate Verdict
0001 V1 SA Construction Model 5 4 1 45% ⚠️ Field renaming drift; assigned_by_id missing
0002 ov.* Namespace Independence 3 0 4 38% Base model leakage FOUND
0003 V3 Association Model 6 3 0 67% ⚠️ Naming inconsistency; missing actor variant
0004 Fleet/Item Association 2 1 2 40% ⚠️ Not yet implemented (may be future work)
V3 Status V3 as Default 7 0 0 100% ✅ V3 is correctly the default

Overall Implementation vs. Design Intent: ~80% Aligned (after correcting false positives)

Key Takeaway: The implementation IS largely aligned with the ADRs. The main issues are: 1. Base model leakage (ADR 0002 violation) — THIS IS THE CRITICAL FINDING 2. Field naming drift between ADR spec and implementation 3. Missing assigned_by_id in ov.membership 4. Incomplete two-layer pattern (96% instead of 100%)


This scorecard was generated by querying the live Odoo instance at enterprise.omnivoltaic.com (Odoo 18.0+e Enterprise) on 2026-06-01. The actual model structures were inspected via XML-RPC and compared against the design intents expressed in the dirac-odoo repo ADRs (docs/adr/0001 through 0004).

Correction (2026-06-01 22:08 GMT+8): Initial automated checks reported false positives for sa_id missing in ov.sa_* models. Manual verification confirmed all 28 models DO have the FK to ov.serviced_account (as account_id or sa_id). The scorecard has been corrected accordingly.