This commit is contained in:
gsinghpal
2026-05-16 13:18:52 -04:00
parent 191a9c82be
commit 9ebf89bde2
1080 changed files with 0 additions and 1197 deletions

Binary file not shown.

View File

@@ -0,0 +1,25 @@
# fusion_accounting_core — Cursor / Claude Context
## Purpose
Foundation for the Fusion Accounting sub-module suite. Owns:
- Three security groups (User / Manager / Admin) shared across all sub-modules
- Shared-field-ownership declarations on `account.move` and `account.reconcile.model`
- Runtime Enterprise-detection helper: `env['ir.module.module']._fusion_is_enterprise_accounting_installed()`
## What lives here
- `models/account_move.py` — declares Enterprise-extension fields with identical
schemas / relation tables. Pure schema-preservation; no business logic.
- `models/account_reconcile_model.py` — same pattern for `created_automatically`
- `models/ir_module_module.py` — Enterprise-detection helpers
- `security/fusion_accounting_security.xml` — privilege + 3 groups + auto-assignment
## Critical rules
- NEVER add business logic to the shared-field models (account_move.py here).
Logic belongs in the feature sub-module that owns it (e.g. fusion_accounting_bank_rec).
- NEVER rename the relation tables for shared M2Ms. They must match Enterprise verbatim
for the dual-ownership pattern to work.
- Shared fields here have NO defaults beyond what Enterprise sets. The point is preservation.
## Cross-references
- Parent design: `fusion_accounting/docs/superpowers/specs/2026-04-18-fusion-accounting-enterprise-takeover-roadmap-design.md` (Section 3)
- Workspace conventions: `/Users/gurpreet/Github/Odoo-Modules/CLAUDE.md`

View File

@@ -0,0 +1,39 @@
# Fusion Accounting Core
Foundation module for the Fusion Accounting suite.
## What it does
- Defines three security groups: Fusion Accounting User / Manager / Administrator
- Auto-promotes Odoo `account.group_account_user` -> Fusion User and
`account.group_account_manager` -> Fusion Admin
- Declares schema-preservation fields on `account.move` and `account.reconcile.model`
so that Enterprise extension fields (deferred revenue links, signing user, etc.)
survive an Enterprise uninstall
- Exposes the helper `env['ir.module.module']._fusion_is_enterprise_accounting_installed()`
## Install
This module never installs alone. Install `fusion_accounting` (the meta-module)
or any of the feature sub-modules — they all depend on `fusion_accounting_core`.
## Uninstall
Uninstalling `fusion_accounting_core` will remove the security groups and the
schema-preservation fields. If Enterprise is also installed, uninstalling
`fusion_accounting_core` will cause Odoo to consider the deferred / signing
fields owned only by Enterprise — which is the original Enterprise-only state
(no data loss, just back to Enterprise-controlled schema).
## Troubleshooting
If users are missing the "Fusion Accounting" privilege section in user settings
after install, the `implied_ids` mechanism only fires for newly-added users.
Backfill existing users via SQL:
INSERT INTO res_groups_users_rel (gid, uid)
SELECT g.res_id, gu.uid
FROM res_groups_users_rel gu
JOIN ir_model_data g ON g.module = 'fusion_accounting_core' AND g.name = 'group_fusion_accounting_user'
JOIN ir_model_data ag ON ag.module = 'account' AND ag.name = 'group_account_user' AND gu.gid = ag.res_id
ON CONFLICT DO NOTHING;

View File

@@ -0,0 +1,28 @@
# UPGRADE_NOTES — fusion_accounting_core
## V19.0.1.0.0 (initial — Phase 0)
### Reference sources
- `RePackaged-Odoo/accounting/account_accountant/models/account_move.py` (Enterprise extension fields read for schema match)
- `RePackaged-Odoo/accounting/account_accountant/models/account_reconcile_model.py` (same)
### Mirror-zone files (none in _core — _core has no Mirror zone)
### Abstract-zone files (all of _core is abstract)
- `models/account_move.py`
- `models/account_reconcile_model.py`
- `models/ir_module_module.py`
### Intentional deltas from Odoo
- Shared-field declarations have NO compute methods, NO @api decorators beyond
basic field types. Enterprise's account_move.py adds compute methods and
business logic; we deliberately do not duplicate them. When Enterprise is
installed, its compute methods run; when it's not, the fields are simply
unused (until a fusion sub-module decides to own that behavior).
### Migrations
- `migrations/19.0.1.0.0/pre-migration.py` — rehome fusion security xml-ids
from module='fusion_accounting' to module='fusion_accounting_core' BEFORE
data-load (avoids unique-constraint crash on upgrade from pre-Phase-0)
- `migrations/19.0.1.0.0/post-migration.py` — idempotent safety-net for the
same rehome (zero-op if pre-migration already ran)

View File

@@ -0,0 +1,6 @@
from . import models
def post_init_hook(env):
"""Initialize coexistence group membership based on current Enterprise install state."""
env['res.users']._fusion_recompute_coexistence_group()

View File

@@ -0,0 +1,34 @@
{
'name': 'Fusion Accounting Core',
'version': '19.0.1.1.0',
'category': 'Accounting/Accounting',
'sequence': 24,
'summary': 'Shared base for the Fusion Accounting sub-module suite (security, shared schema, runtime helpers).',
'description': """
Fusion Accounting Core
======================
Foundation for the Fusion Accounting sub-modules. Owns:
- Three security groups (User, Manager, Admin) shared across all fusion sub-modules
- Shared-field declarations on Community account models so deferred-revenue,
signing-user, and similar Enterprise-extension fields survive Enterprise uninstall
- Runtime helper for detecting Odoo Enterprise accounting modules
This module never works alone. Install fusion_accounting (the meta-module)
or one of fusion_accounting_ai, fusion_accounting_bank_rec, etc.
Built by Nexa Systems Inc.
""",
'author': 'Nexa Systems Inc.',
'website': 'https://nexasystems.ca',
'support': 'support@nexasystems.ca',
'maintainer': 'Nexa Systems Inc.',
'depends': ['account', 'mail'],
'data': [
'security/fusion_accounting_security.xml',
'security/ir.model.access.csv',
],
'installable': True,
'application': False,
'license': 'OPL-1',
'post_init_hook': 'post_init_hook',
}

View File

@@ -0,0 +1,149 @@
# Graph Report - /Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core (2026-04-22)
## Corpus Check
- 15 files · ~4,588 words
- Verdict: corpus is large enough that graph structure adds value.
## Summary
- 66 nodes · 62 edges · 17 communities detected
- Extraction: 87% EXTRACTED · 13% INFERRED · 0% AMBIGUOUS · INFERRED: 8 edges (avg confidence: 0.8)
- Token cost: 0 input · 0 output
## Community Hubs (Navigation)
- [[_COMMUNITY_Community 0|Community 0]]
- [[_COMMUNITY_Community 1|Community 1]]
- [[_COMMUNITY_Community 2|Community 2]]
- [[_COMMUNITY_Community 3|Community 3]]
- [[_COMMUNITY_Community 4|Community 4]]
- [[_COMMUNITY_Community 5|Community 5]]
- [[_COMMUNITY_Community 6|Community 6]]
- [[_COMMUNITY_Community 7|Community 7]]
- [[_COMMUNITY_Community 8|Community 8]]
- [[_COMMUNITY_Community 9|Community 9]]
- [[_COMMUNITY_Community 10|Community 10]]
- [[_COMMUNITY_Community 11|Community 11]]
- [[_COMMUNITY_Community 12|Community 12]]
- [[_COMMUNITY_Community 13|Community 13]]
- [[_COMMUNITY_Community 14|Community 14]]
- [[_COMMUNITY_Community 15|Community 15]]
- [[_COMMUNITY_Community 16|Community 16]]
## God Nodes (most connected - your core abstractions)
1. `TestSharedFieldOwnership` - 8 edges
2. `TestCoexistenceGroup` - 6 edges
3. `_fusion_recompute_coexistence_group()` - 6 edges
4. `TestEnterpriseDetection` - 5 edges
5. `_fusion_is_enterprise_accounting_installed()` - 5 edges
6. `TestSharedFieldBankStatementLine` - 4 edges
7. `IrModuleModule` - 4 edges
8. `post_init_hook()` - 3 edges
9. `Initialize coexistence group membership based on current Enterprise install stat` - 1 edges
10. `Rehome the fusion security xml-ids to fusion_accounting_core BEFORE data-load.` - 1 edges
## Surprising Connections (you probably didn't know these)
- `post_init_hook()` --calls--> `_fusion_recompute_coexistence_group()` [INFERRED]
/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/__init__.py → /Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/models/res_users.py
- `_fusion_recompute_coexistence_group()` --calls--> `_fusion_is_enterprise_accounting_installed()` [INFERRED]
/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/models/res_users.py → /Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/models/ir_module_module.py
## Communities
### Community 0 - "Community 0"
Cohesion: 0.18
Nodes (6): The recompute helper must be callable on res.users., The 'show when Enterprise absent' group must exist and have computed membership., TestCoexistenceGroup, Verify fusion_accounting_core declares the Enterprise extension fields on ac, TestSharedFieldBankStatementLine, TransactionCase
### Community 1 - "Community 1"
Cohesion: 0.24
Nodes (5): IrModuleModule, Recompute the coexistence group after install state changes., Recompute the coexistence group after uninstall state changes. The MRO, Recompute the coexistence group after the lower-level uninstall., _fusion_recompute_coexistence_group()
### Community 2 - "Community 2"
Cohesion: 0.22
Nodes (3): The shared M2M relation table must be named identically to Enterprise's, Verify fusion_accounting_core declares the Enterprise extension fields on ac, TestSharedFieldOwnership
### Community 3 - "Community 3"
Cohesion: 0.25
Nodes (5): _fusion_is_enterprise_accounting_installed(), A user is in the group iff Enterprise accounting is NOT installed. We c, Helper should return True iff one of the known Enterprise modules is installed., Verify the helper that detects Odoo Enterprise accounting installs., TestEnterpriseDetection
### Community 4 - "Community 4"
Cohesion: 0.67
Nodes (1): Rehome the fusion security xml-ids to fusion_accounting_core BEFORE data-load.
### Community 5 - "Community 5"
Cohesion: 0.67
Nodes (1): Safety-net reassignment of security xml-ids to fusion_accounting_core. The actu
### Community 6 - "Community 6"
Cohesion: 1.0
Nodes (2): post_init_hook(), Initialize coexistence group membership based on current Enterprise install stat
### Community 7 - "Community 7"
Cohesion: 0.67
Nodes (2): AccountMove, Shared-field-ownership declarations for account.move. Per the roadmap (Section
### Community 8 - "Community 8"
Cohesion: 0.67
Nodes (2): AccountReconcileModel, Shared-field-ownership for account.reconcile.model. Mirrors the single field En
### Community 9 - "Community 9"
Cohesion: 0.67
Nodes (2): Coexistence group membership recomputation., ResUsers
### Community 10 - "Community 10"
Cohesion: 0.67
Nodes (2): AccountBankStatementLine, Shared-field-ownership for account.bank.statement.line. Enterprise's account_ac
### Community 11 - "Community 11"
Cohesion: 1.0
Nodes (0):
### Community 12 - "Community 12"
Cohesion: 1.0
Nodes (0):
### Community 13 - "Community 13"
Cohesion: 1.0
Nodes (0):
### Community 14 - "Community 14"
Cohesion: 1.0
Nodes (1): True if any Odoo Enterprise accounting module is installed in this DB.
### Community 15 - "Community 15"
Cohesion: 1.0
Nodes (1): True if a specific module is installed.
### Community 16 - "Community 16"
Cohesion: 1.0
Nodes (1): Maintain the two coexistence groups based on Enterprise presence. - ``g
## Knowledge Gaps
- **25 isolated node(s):** `Initialize coexistence group membership based on current Enterprise install stat`, `Rehome the fusion security xml-ids to fusion_accounting_core BEFORE data-load.`, `Safety-net reassignment of security xml-ids to fusion_accounting_core. The actu`, `Verify fusion_accounting_core declares the Enterprise extension fields on ac`, `Verify fusion_accounting_core declares the Enterprise extension fields on ac` (+20 more)
These have ≤1 connection - possible missing edges or undocumented components.
- **Thin community `Community 11`** (1 nodes): `__init__.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 12`** (1 nodes): `__init__.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 13`** (1 nodes): `__manifest__.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 14`** (1 nodes): `True if any Odoo Enterprise accounting module is installed in this DB.`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 15`** (1 nodes): `True if a specific module is installed.`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 16`** (1 nodes): `Maintain the two coexistence groups based on Enterprise presence. - ``g`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
## Suggested Questions
_Questions this graph is uniquely positioned to answer:_
- **Why does `_fusion_is_enterprise_accounting_installed()` connect `Community 3` to `Community 1`?**
_High betweenness centrality (0.232) - this node is a cross-community bridge._
- **Why does `_fusion_recompute_coexistence_group()` connect `Community 1` to `Community 9`, `Community 3`, `Community 6`?**
_High betweenness centrality (0.200) - this node is a cross-community bridge._
- **Why does `TestSharedFieldOwnership` connect `Community 2` to `Community 0`?**
_High betweenness centrality (0.151) - this node is a cross-community bridge._
- **Are the 5 inferred relationships involving `_fusion_recompute_coexistence_group()` (e.g. with `post_init_hook()` and `.button_immediate_install()`) actually correct?**
_`_fusion_recompute_coexistence_group()` has 5 INFERRED edges - model-reasoned connections that need verification._
- **Are the 4 inferred relationships involving `_fusion_is_enterprise_accounting_installed()` (e.g. with `.test_membership_matches_enterprise_state()` and `.test_helper_returns_bool()`) actually correct?**
_`_fusion_is_enterprise_accounting_installed()` has 4 INFERRED edges - model-reasoned connections that need verification._
- **What connects `Initialize coexistence group membership based on current Enterprise install stat`, `Rehome the fusion security xml-ids to fusion_accounting_core BEFORE data-load.`, `Safety-net reassignment of security xml-ids to fusion_accounting_core. The actu` to the rest of the system?**
_25 weakly-connected nodes found - possible documentation gaps or missing edges._

View File

@@ -0,0 +1 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_fusion_accounting_core_tests_test_shared_field_bank_statement_py", "label": "test_shared_field_bank_statement.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/tests/test_shared_field_bank_statement.py", "source_location": "L1"}, {"id": "test_shared_field_bank_statement_testsharedfieldbankstatementline", "label": "TestSharedFieldBankStatementLine", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/tests/test_shared_field_bank_statement.py", "source_location": "L5"}, {"id": "transactioncase", "label": "TransactionCase", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "test_shared_field_bank_statement_testsharedfieldbankstatementline_test_cron_last_check_field_exists", "label": ".test_cron_last_check_field_exists()", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/tests/test_shared_field_bank_statement.py", "source_location": "L9"}, {"id": "test_shared_field_bank_statement_rationale_6", "label": "Verify fusion_accounting_core declares the Enterprise extension fields on ac", "file_type": "rationale", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/tests/test_shared_field_bank_statement.py", "source_location": "L6"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_fusion_accounting_core_tests_test_shared_field_bank_statement_py", "target": "odoo_tests_common", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/tests/test_shared_field_bank_statement.py", "source_location": "L1", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_core_tests_test_shared_field_bank_statement_py", "target": "test_shared_field_bank_statement_testsharedfieldbankstatementline", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/tests/test_shared_field_bank_statement.py", "source_location": "L5", "weight": 1.0}, {"source": "test_shared_field_bank_statement_testsharedfieldbankstatementline", "target": "transactioncase", "relation": "inherits", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/tests/test_shared_field_bank_statement.py", "source_location": "L5", "weight": 1.0}, {"source": "test_shared_field_bank_statement_testsharedfieldbankstatementline", "target": "test_shared_field_bank_statement_testsharedfieldbankstatementline_test_cron_last_check_field_exists", "relation": "method", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/tests/test_shared_field_bank_statement.py", "source_location": "L9", "weight": 1.0}, {"source": "test_shared_field_bank_statement_rationale_6", "target": "test_shared_field_bank_statement_testsharedfieldbankstatementline", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/tests/test_shared_field_bank_statement.py", "source_location": "L6", "weight": 1.0}], "raw_calls": [{"caller_nid": "test_shared_field_bank_statement_testsharedfieldbankstatementline_test_cron_last_check_field_exists", "callee": "assertIn", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/tests/test_shared_field_bank_statement.py", "source_location": "L11"}, {"caller_nid": "test_shared_field_bank_statement_testsharedfieldbankstatementline_test_cron_last_check_field_exists", "callee": "assertEqual", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/tests/test_shared_field_bank_statement.py", "source_location": "L14"}]}

View File

@@ -0,0 +1 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_fusion_accounting_core_models_account_reconcile_model_py", "label": "account_reconcile_model.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/models/account_reconcile_model.py", "source_location": "L1"}, {"id": "account_reconcile_model_accountreconcilemodel", "label": "AccountReconcileModel", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/models/account_reconcile_model.py", "source_location": "L10"}, {"id": "account_reconcile_model_rationale_1", "label": "Shared-field-ownership for account.reconcile.model. Mirrors the single field En", "file_type": "rationale", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/models/account_reconcile_model.py", "source_location": "L1"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_fusion_accounting_core_models_account_reconcile_model_py", "target": "odoo", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/models/account_reconcile_model.py", "source_location": "L7", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_core_models_account_reconcile_model_py", "target": "account_reconcile_model_accountreconcilemodel", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/models/account_reconcile_model.py", "source_location": "L10", "weight": 1.0}, {"source": "account_reconcile_model_rationale_1", "target": "users_gurpreet_github_odoo_modules_fusion_accounting_core_models_account_reconcile_model_py", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/models/account_reconcile_model.py", "source_location": "L1", "weight": 1.0}], "raw_calls": []}

View File

@@ -0,0 +1 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_fusion_accounting_core_migrations_19_0_1_0_0_post_migration_py", "label": "post-migration.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/migrations/19.0.1.0.0/post-migration.py", "source_location": "L1"}, {"id": "post_migration_migrate", "label": "migrate()", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/migrations/19.0.1.0.0/post-migration.py", "source_location": "L31"}, {"id": "post_migration_rationale_1", "label": "Safety-net reassignment of security xml-ids to fusion_accounting_core. The actu", "file_type": "rationale", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/migrations/19.0.1.0.0/post-migration.py", "source_location": "L1"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_fusion_accounting_core_migrations_19_0_1_0_0_post_migration_py", "target": "logging", "relation": "imports", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/migrations/19.0.1.0.0/post-migration.py", "source_location": "L17", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_core_migrations_19_0_1_0_0_post_migration_py", "target": "post_migration_migrate", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/migrations/19.0.1.0.0/post-migration.py", "source_location": "L31", "weight": 1.0}, {"source": "post_migration_rationale_1", "target": "users_gurpreet_github_odoo_modules_fusion_accounting_core_migrations_19_0_1_0_0_post_migration_py", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/migrations/19.0.1.0.0/post-migration.py", "source_location": "L1", "weight": 1.0}], "raw_calls": [{"caller_nid": "post_migration_migrate", "callee": "execute", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/migrations/19.0.1.0.0/post-migration.py", "source_location": "L32"}, {"caller_nid": "post_migration_migrate", "callee": "list", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/migrations/19.0.1.0.0/post-migration.py", "source_location": "L39"}, {"caller_nid": "post_migration_migrate", "callee": "info", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/migrations/19.0.1.0.0/post-migration.py", "source_location": "L42"}]}

View File

@@ -0,0 +1 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_fusion_accounting_core_tests_init_py", "label": "__init__.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/tests/__init__.py", "source_location": "L1"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_fusion_accounting_core_tests_init_py", "target": "users_gurpreet_github_odoo_modules_fusion_accounting_core_tests_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/tests/__init__.py", "source_location": "L1", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_core_tests_init_py", "target": "users_gurpreet_github_odoo_modules_fusion_accounting_core_tests_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/tests/__init__.py", "source_location": "L2", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_core_tests_init_py", "target": "users_gurpreet_github_odoo_modules_fusion_accounting_core_tests_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/tests/__init__.py", "source_location": "L3", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_core_tests_init_py", "target": "users_gurpreet_github_odoo_modules_fusion_accounting_core_tests_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/tests/__init__.py", "source_location": "L4", "weight": 1.0}], "raw_calls": []}

View File

@@ -0,0 +1 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_fusion_accounting_core_models_init_py", "label": "__init__.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/models/__init__.py", "source_location": "L1"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_fusion_accounting_core_models_init_py", "target": "users_gurpreet_github_odoo_modules_fusion_accounting_core_models_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/models/__init__.py", "source_location": "L1", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_core_models_init_py", "target": "users_gurpreet_github_odoo_modules_fusion_accounting_core_models_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/models/__init__.py", "source_location": "L2", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_core_models_init_py", "target": "users_gurpreet_github_odoo_modules_fusion_accounting_core_models_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/models/__init__.py", "source_location": "L3", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_core_models_init_py", "target": "users_gurpreet_github_odoo_modules_fusion_accounting_core_models_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/models/__init__.py", "source_location": "L4", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_core_models_init_py", "target": "users_gurpreet_github_odoo_modules_fusion_accounting_core_models_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/models/__init__.py", "source_location": "L5", "weight": 1.0}], "raw_calls": []}

View File

@@ -0,0 +1 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_fusion_accounting_core_models_account_bank_statement_line_py", "label": "account_bank_statement_line.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/models/account_bank_statement_line.py", "source_location": "L1"}, {"id": "account_bank_statement_line_accountbankstatementline", "label": "AccountBankStatementLine", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/models/account_bank_statement_line.py", "source_location": "L12"}, {"id": "account_bank_statement_line_rationale_1", "label": "Shared-field-ownership for account.bank.statement.line. Enterprise's account_ac", "file_type": "rationale", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/models/account_bank_statement_line.py", "source_location": "L1"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_fusion_accounting_core_models_account_bank_statement_line_py", "target": "odoo", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/models/account_bank_statement_line.py", "source_location": "L9", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_core_models_account_bank_statement_line_py", "target": "account_bank_statement_line_accountbankstatementline", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/models/account_bank_statement_line.py", "source_location": "L12", "weight": 1.0}, {"source": "account_bank_statement_line_rationale_1", "target": "users_gurpreet_github_odoo_modules_fusion_accounting_core_models_account_bank_statement_line_py", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/models/account_bank_statement_line.py", "source_location": "L1", "weight": 1.0}], "raw_calls": []}

View File

@@ -0,0 +1 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_fusion_accounting_core_models_account_move_py", "label": "account_move.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/models/account_move.py", "source_location": "L1"}, {"id": "account_move_accountmove", "label": "AccountMove", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/models/account_move.py", "source_location": "L19"}, {"id": "account_move_rationale_1", "label": "Shared-field-ownership declarations for account.move. Per the roadmap (Section", "file_type": "rationale", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/models/account_move.py", "source_location": "L1"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_fusion_accounting_core_models_account_move_py", "target": "odoo", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/models/account_move.py", "source_location": "L16", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_core_models_account_move_py", "target": "account_move_accountmove", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/models/account_move.py", "source_location": "L19", "weight": 1.0}, {"source": "account_move_rationale_1", "target": "users_gurpreet_github_odoo_modules_fusion_accounting_core_models_account_move_py", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/models/account_move.py", "source_location": "L1", "weight": 1.0}], "raw_calls": []}

View File

@@ -0,0 +1 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_fusion_accounting_core_migrations_19_0_1_0_0_pre_migration_py", "label": "pre-migration.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/migrations/19.0.1.0.0/pre-migration.py", "source_location": "L1"}, {"id": "pre_migration_migrate", "label": "migrate()", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/migrations/19.0.1.0.0/pre-migration.py", "source_location": "L45"}, {"id": "pre_migration_rationale_1", "label": "Rehome the fusion security xml-ids to fusion_accounting_core BEFORE data-load.", "file_type": "rationale", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/migrations/19.0.1.0.0/pre-migration.py", "source_location": "L1"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_fusion_accounting_core_migrations_19_0_1_0_0_pre_migration_py", "target": "logging", "relation": "imports", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/migrations/19.0.1.0.0/pre-migration.py", "source_location": "L31", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_core_migrations_19_0_1_0_0_pre_migration_py", "target": "pre_migration_migrate", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/migrations/19.0.1.0.0/pre-migration.py", "source_location": "L45", "weight": 1.0}, {"source": "pre_migration_rationale_1", "target": "users_gurpreet_github_odoo_modules_fusion_accounting_core_migrations_19_0_1_0_0_pre_migration_py", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/migrations/19.0.1.0.0/pre-migration.py", "source_location": "L1", "weight": 1.0}], "raw_calls": [{"caller_nid": "pre_migration_migrate", "callee": "execute", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/migrations/19.0.1.0.0/pre-migration.py", "source_location": "L46"}, {"caller_nid": "pre_migration_migrate", "callee": "list", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/migrations/19.0.1.0.0/pre-migration.py", "source_location": "L53"}, {"caller_nid": "pre_migration_migrate", "callee": "info", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/migrations/19.0.1.0.0/pre-migration.py", "source_location": "L56"}]}

View File

@@ -0,0 +1 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_fusion_accounting_core_init_py", "label": "__init__.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/__init__.py", "source_location": "L1"}, {"id": "init_post_init_hook", "label": "post_init_hook()", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/__init__.py", "source_location": "L4"}, {"id": "init_rationale_5", "label": "Initialize coexistence group membership based on current Enterprise install stat", "file_type": "rationale", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/__init__.py", "source_location": "L5"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_fusion_accounting_core_init_py", "target": "users_gurpreet_github_odoo_modules_fusion_accounting_core_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/__init__.py", "source_location": "L1", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_core_init_py", "target": "init_post_init_hook", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/__init__.py", "source_location": "L4", "weight": 1.0}, {"source": "init_rationale_5", "target": "init_post_init_hook", "relation": "rationale_for", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/__init__.py", "source_location": "L5", "weight": 1.0}], "raw_calls": [{"caller_nid": "init_post_init_hook", "callee": "_fusion_recompute_coexistence_group", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/__init__.py", "source_location": "L6"}]}

View File

@@ -0,0 +1 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_fusion_accounting_core_manifest_py", "label": "__manifest__.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_core/__manifest__.py", "source_location": "L1"}], "edges": [], "raw_calls": []}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,47 @@
"""Safety-net reassignment of security xml-ids to fusion_accounting_core.
The actual rename lives in pre-migration.py — it MUST run before data-load
to avoid creating duplicate res.groups records and hitting the (module,
name) unique constraint on ir_model_data. This post-migration is a
belt-and-suspenders no-op for the common case: if pre-migration already
ran, this UPDATE matches zero rows.
It also catches a rare edge case: fusion_accounting_ai.post-migration.py
runs an identical UPDATE to cover cross-module upgrade ordering, so both
modules redundantly ensure the rows land in the right module regardless
of which upgrade runs first.
Idempotent: running it a second time matches zero rows.
"""
import logging
_logger = logging.getLogger(__name__)
SECURITY_NAMES = (
'module_category_fusion_accounting',
'res_groups_privilege_fusion_accounting',
'group_fusion_accounting_user',
'group_fusion_accounting_manager',
'group_fusion_accounting_admin',
)
def migrate(cr, version):
cr.execute(
"""
UPDATE ir_model_data
SET module = 'fusion_accounting_core'
WHERE module = 'fusion_accounting'
AND name = ANY(%s)
""",
(list(SECURITY_NAMES),),
)
moved = cr.rowcount
_logger.info(
"fusion_accounting_core post-migration: reassigned %d security rows "
"from module='fusion_accounting' to module='fusion_accounting_core' "
"(usually zero; pre-migration already handled the rename)",
moved,
)

View File

@@ -0,0 +1,62 @@
"""Rehome the fusion security xml-ids to fusion_accounting_core BEFORE data-load.
Pre-Phase-0, the three fusion security groups (user, manager, admin), the
module category and the privilege all lived in module='fusion_accounting'.
Post-Phase-0 (Task 16) they moved into module='fusion_accounting_core'.
Running this rename in pre-migration (rather than post-migration) is
essential: Odoo's XML data-load looks up records by (module, name) in
ir_model_data. If the old row still has module='fusion_accounting' when
data-load runs, Odoo will not find a match for
'fusion_accounting_core.group_fusion_accounting_user' and will CREATE a
brand-new res.groups record plus a new ir_model_data row. That leaves the
database with two groups per name:
1. The ORIGINAL group (still tagged module='fusion_accounting') that every
existing user is linked to via res_groups_users_rel.
2. A FRESH empty group (newly tagged module='fusion_accounting_core').
The subsequent post-migration UPDATE...SET module='fusion_accounting_core'
would then violate the (module, name) unique constraint on ir_model_data
and the upgrade transaction would roll back.
By renaming ir_model_data rows BEFORE data-load, Odoo finds the existing
row (now tagged fusion_accounting_core.*), UPDATEs the res.groups record
in place with the XML-defined values, and the user-group links are
preserved untouched.
Idempotent: running this a second time matches zero rows.
"""
import logging
_logger = logging.getLogger(__name__)
SECURITY_NAMES = (
'module_category_fusion_accounting',
'res_groups_privilege_fusion_accounting',
'group_fusion_accounting_user',
'group_fusion_accounting_manager',
'group_fusion_accounting_admin',
)
def migrate(cr, version):
cr.execute(
"""
UPDATE ir_model_data
SET module = 'fusion_accounting_core'
WHERE module = 'fusion_accounting'
AND name = ANY(%s)
""",
(list(SECURITY_NAMES),),
)
moved = cr.rowcount
_logger.info(
"fusion_accounting_core pre-migration: renamed %d security rows "
"from module='fusion_accounting' to module='fusion_accounting_core' "
"before data-load (idempotent; non-zero only on first upgrade from "
"pre-Phase-0)",
moved,
)

View File

@@ -0,0 +1,5 @@
from . import ir_module_module
from . import res_users
from . import account_move
from . import account_reconcile_model
from . import account_bank_statement_line

View File

@@ -0,0 +1,15 @@
"""Shared-field-ownership for account.bank.statement.line.
Enterprise's account_accountant adds cron_last_check (timestamp of the last
auto-reconcile cron run for the line). By declaring it here with the same
schema, fusion_accounting_core becomes a co-owner so the column persists
when account_accountant uninstalls.
"""
from odoo import fields, models
class AccountBankStatementLine(models.Model):
_inherit = "account.bank.statement.line"
cron_last_check = fields.Datetime(copy=False)

View File

@@ -0,0 +1,56 @@
"""Shared-field-ownership declarations for account.move.
Per the roadmap (Section 3.3), these fields exist in Odoo Enterprise's
account_accountant module. By declaring them here with identical schemas
and identical relation tables, fusion_accounting_core becomes a co-owner.
When Enterprise uninstalls, Odoo's module registry sees fusion still owns
the fields and preserves the columns / relation tables, so the data
(deferred revenue links, signing user, etc.) survives uninstall.
The fields here have NO compute methods, NO defaults beyond what Enterprise
provides, NO views. They're pure schema-preservation declarations. Any
business logic that operates on these fields lives in Enterprise (when
present) or in a future fusion sub-module that opts to own that behavior.
"""
from odoo import fields, models
class AccountMove(models.Model):
_inherit = "account.move"
deferred_move_ids = fields.Many2many(
comodel_name='account.move',
relation='account_move_deferred_rel',
column1='original_move_id',
column2='deferred_move_id',
copy=False,
string="Deferred Entries",
)
deferred_original_move_ids = fields.Many2many(
comodel_name='account.move',
relation='account_move_deferred_rel',
column1='deferred_move_id',
column2='original_move_id',
copy=False,
string="Original Invoices",
)
deferred_entry_type = fields.Selection(
selection=[
('expense', 'Deferred Expense'),
('revenue', 'Deferred Revenue'),
],
copy=False,
string="Deferred Entry Type",
)
signing_user = fields.Many2one(
comodel_name='res.users',
copy=False,
string="Signing User",
)
payment_state_before_switch = fields.Char(
copy=False,
string="Payment State Before Switch",
)

View File

@@ -0,0 +1,17 @@
"""Shared-field-ownership for account.reconcile.model.
Mirrors the single field Enterprise's account_accountant adds to the
Community account.reconcile.model: created_automatically.
"""
from odoo import fields, models
class AccountReconcileModel(models.Model):
_inherit = "account.reconcile.model"
created_automatically = fields.Boolean(
default=False,
copy=False,
string="Created Automatically",
)

View File

@@ -0,0 +1,55 @@
from odoo import api, models
# Modules considered "Odoo Enterprise accounting" for the purpose of feature gating.
# A client is "on Enterprise" if any of these are installed; fusion_accounting_*
# replacement modules will hide their menus when Enterprise is present (replace mode
# vs. augment mode is configurable in Settings).
ENTERPRISE_ACCOUNTING_MODULES = (
'account_accountant',
'account_reports',
'accountant',
)
class IrModuleModule(models.Model):
_inherit = "ir.module.module"
@api.model
def _fusion_is_enterprise_accounting_installed(self):
"""True if any Odoo Enterprise accounting module is installed in this DB."""
return bool(self.sudo().search_count([
('name', 'in', list(ENTERPRISE_ACCOUNTING_MODULES)),
('state', '=', 'installed'),
]))
@api.model
def _fusion_is_module_installed(self, module_name):
"""True if a specific module is installed."""
return bool(self.sudo().search_count([
('name', '=', module_name),
('state', '=', 'installed'),
]))
def button_immediate_install(self):
"""Recompute the coexistence group after install state changes."""
result = super().button_immediate_install()
self.env['res.users']._fusion_recompute_coexistence_group()
return result
def button_immediate_uninstall(self):
"""Recompute the coexistence group after uninstall state changes.
The MRO chains into fusion_accounting_migration's override (which runs
the safety guard before calling super); we recompute only after the
whole chain completes.
"""
result = super().button_immediate_uninstall()
self.env['res.users']._fusion_recompute_coexistence_group()
return result
def module_uninstall(self):
"""Recompute the coexistence group after the lower-level uninstall."""
result = super().module_uninstall()
self.env['res.users']._fusion_recompute_coexistence_group()
return result

View File

@@ -0,0 +1,53 @@
"""Coexistence group membership recomputation."""
from odoo import api, models
class ResUsers(models.Model):
_inherit = "res.users"
@api.model
def _fusion_recompute_coexistence_group(self):
"""Maintain the two coexistence groups based on Enterprise presence.
- ``group_fusion_show_when_enterprise_absent``: members = all internal
users when NO Enterprise accounting module is installed. Used to
unhide Fusion menus that would conflict with Enterprise UIs.
- ``group_fusion_show_when_enterprise_present``: members = all internal
users when AT LEAST ONE Enterprise accounting module IS installed.
Used to hide migration/transitional UIs once Enterprise has been
uninstalled (so the user doesn't see "Migrate from Enterprise" with
nothing to migrate).
The two groups are mutually exclusive at any moment in time, but a
user can transition between them as Enterprise modules are installed
or uninstalled. Idempotent; safe to call multiple times.
Called from ir.module.module.button_immediate_install / uninstall
overrides.
"""
absent_group = self.env.ref(
'fusion_accounting_core.group_fusion_show_when_enterprise_absent',
raise_if_not_found=False,
)
present_group = self.env.ref(
'fusion_accounting_core.group_fusion_show_when_enterprise_present',
raise_if_not_found=False,
)
if not absent_group and not present_group:
return
enterprise_installed = self.env['ir.module.module']._fusion_is_enterprise_accounting_installed()
all_internal = self.sudo().search([('share', '=', False)])
if enterprise_installed:
if absent_group:
absent_group.sudo().write({'user_ids': [(5, 0, 0)]})
if present_group:
present_group.sudo().write({'user_ids': [(6, 0, all_internal.ids)]})
else:
if absent_group:
absent_group.sudo().write({'user_ids': [(6, 0, all_internal.ids)]})
if present_group:
present_group.sudo().write({'user_ids': [(5, 0, 0)]})

View File

@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Module Category -->
<record id="module_category_fusion_accounting" model="ir.module.category">
<field name="name">Fusion Accounting</field>
<field name="sequence">25</field>
</record>
<!-- Groups Privilege -->
<record id="res_groups_privilege_fusion_accounting" model="res.groups.privilege">
<field name="name">Fusion Accounting</field>
<field name="category_id" ref="module_category_fusion_accounting"/>
</record>
<!-- User Group (Staff) -->
<record id="group_fusion_accounting_user" model="res.groups">
<field name="name">User</field>
<field name="sequence">10</field>
<field name="implied_ids" eval="[(4, ref('account.group_account_user'))]"/>
<field name="privilege_id" ref="res_groups_privilege_fusion_accounting"/>
</record>
<!-- Manager Group -->
<record id="group_fusion_accounting_manager" model="res.groups">
<field name="name">Manager</field>
<field name="sequence">20</field>
<field name="implied_ids" eval="[(4, ref('group_fusion_accounting_user'))]"/>
<field name="privilege_id" ref="res_groups_privilege_fusion_accounting"/>
</record>
<!-- Admin Group -->
<record id="group_fusion_accounting_admin" model="res.groups">
<field name="name">Administrator</field>
<field name="sequence">30</field>
<field name="implied_ids" eval="[(4, ref('group_fusion_accounting_manager'))]"/>
<field name="privilege_id" ref="res_groups_privilege_fusion_accounting"/>
</record>
<!-- Auto-assign: Accounting users get Fusion User; Advisers get Admin -->
<record id="account.group_account_user" model="res.groups">
<field name="implied_ids" eval="[(4, ref('group_fusion_accounting_user'))]"/>
</record>
<record id="account.group_account_manager" model="res.groups">
<field name="implied_ids" eval="[(4, ref('group_fusion_accounting_admin'))]"/>
</record>
<!-- Phase 1: dynamic coexistence group -->
<record id="group_fusion_show_when_enterprise_absent" model="res.groups">
<field name="name">Fusion: Show menus when Enterprise absent</field>
<field name="comment">Computed group. Membership: all internal users when no Enterprise accounting module is installed. Used to hide fusion sub-module menus that would conflict with Enterprise UIs.</field>
</record>
<!-- Phase 8: inverse coexistence group \u2014 visible only when Enterprise IS present.
Used to hide migration/transitional UIs once the migration is complete and
Enterprise has been uninstalled. -->
<record id="group_fusion_show_when_enterprise_present" model="res.groups">
<field name="name">Fusion: Show menus when Enterprise present</field>
<field name="comment">Computed group. Membership: all internal users WHEN at least one Enterprise accounting module is installed. Used to hide migration/transitional UIs that are irrelevant once Enterprise has been uninstalled.</field>
</record>
</odoo>

View File

@@ -0,0 +1 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

View File

@@ -0,0 +1,4 @@
from . import test_enterprise_detection
from . import test_shared_field_ownership
from . import test_shared_field_bank_statement
from . import test_coexistence_group

View File

@@ -0,0 +1,46 @@
from odoo.tests.common import TransactionCase, tagged
@tagged('post_install', '-at_install')
class TestCoexistenceGroup(TransactionCase):
"""The 'show when Enterprise absent' group must exist and have computed membership."""
def test_group_exists(self):
group = self.env.ref(
'fusion_accounting_core.group_fusion_show_when_enterprise_absent',
raise_if_not_found=False,
)
self.assertTrue(group, "Coexistence group must exist")
def test_membership_matches_enterprise_state(self):
"""A user is in the group iff Enterprise accounting is NOT installed.
We can't toggle Enterprise mid-test, so just assert the current state
matches: if Enterprise is installed, group should have 0 members; if
not, the group should include all internal users.
"""
group = self.env.ref(
'fusion_accounting_core.group_fusion_show_when_enterprise_absent'
)
enterprise_installed = self.env['ir.module.module']._fusion_is_enterprise_accounting_installed()
all_internal = self.env['res.users'].sudo().search([('share', '=', False)])
if enterprise_installed:
self.assertEqual(
len(group.user_ids), 0,
"Enterprise installed -> coexistence group should be empty",
)
else:
self.assertEqual(
set(group.user_ids.ids), set(all_internal.ids),
"Enterprise absent -> coexistence group should contain all internal users",
)
def test_recompute_method_exists(self):
"""The recompute helper must be callable on res.users."""
self.assertTrue(
callable(getattr(
self.env['res.users'],
'_fusion_recompute_coexistence_group',
None,
))
)

View File

@@ -0,0 +1,20 @@
from odoo.tests.common import TransactionCase, tagged
@tagged('post_install', '-at_install')
class TestEnterpriseDetection(TransactionCase):
"""Verify the helper that detects Odoo Enterprise accounting installs."""
def test_helper_returns_bool(self):
result = self.env['ir.module.module']._fusion_is_enterprise_accounting_installed()
self.assertIsInstance(result, bool)
def test_helper_matches_actual_state(self):
"""Helper should return True iff one of the known Enterprise modules is installed."""
installed = self.env['ir.module.module'].sudo().search_count([
('name', 'in', ['account_accountant', 'account_reports', 'accountant']),
('state', '=', 'installed'),
])
expected = bool(installed)
actual = self.env['ir.module.module']._fusion_is_enterprise_accounting_installed()
self.assertEqual(actual, expected)

View File

@@ -0,0 +1,14 @@
from odoo.tests.common import TransactionCase, tagged
@tagged('post_install', '-at_install')
class TestSharedFieldBankStatementLine(TransactionCase):
"""Verify fusion_accounting_core declares the Enterprise extension fields
on account.bank.statement.line so they survive Enterprise uninstall."""
def test_cron_last_check_field_exists(self):
Line = self.env['account.bank.statement.line']
self.assertIn('cron_last_check', Line._fields,
"cron_last_check must be declared on account.bank.statement.line "
"(shared-field-ownership with account_accountant)")
self.assertEqual(Line._fields['cron_last_check'].type, 'datetime')

View File

@@ -0,0 +1,32 @@
from odoo.tests.common import TransactionCase, tagged
@tagged('post_install', '-at_install')
class TestSharedFieldOwnership(TransactionCase):
"""Verify fusion_accounting_core declares the Enterprise extension fields
on account.move and account.reconcile.model, so they survive Enterprise uninstall."""
def test_account_move_deferred_fields_exist(self):
Move = self.env['account.move']
for fname in ('deferred_move_ids', 'deferred_original_move_ids', 'deferred_entry_type'):
self.assertIn(fname, Move._fields, f"{fname!r} must exist on account.move")
def test_account_move_signing_user_exists(self):
Move = self.env['account.move']
self.assertIn('signing_user', Move._fields)
def test_account_move_payment_state_before_switch_exists(self):
Move = self.env['account.move']
self.assertIn('payment_state_before_switch', Move._fields)
def test_account_reconcile_model_created_automatically_exists(self):
Model = self.env['account.reconcile.model']
self.assertIn('created_automatically', Model._fields)
def test_deferred_relation_table_name_matches_enterprise(self):
"""The shared M2M relation table must be named identically to Enterprise's
so dual ownership works (Enterprise drops field => fusion preserves table)."""
f = self.env['account.move']._fields['deferred_move_ids']
self.assertEqual(f.relation, 'account_move_deferred_rel')
self.assertEqual(f.column1, 'original_move_id')
self.assertEqual(f.column2, 'deferred_move_id')