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 @@
from . import models

View File

@@ -0,0 +1,59 @@
{
'name': 'Fusion Accounting - HR Payroll Bridge',
'version': '19.0.1.0.0',
'category': 'Human Resources/Payroll',
'summary': 'Bridges payroll (hr_payroll) to accounting via account.move creation when payslips are validated.',
'description': """
Fusion Accounting - HR Payroll Bridge
=====================================
A Fusion-native replacement for Odoo Enterprise's ``hr_payroll_account``
module. Removes Westin's last payroll-accounting dependency on the
Enterprise ``accountant`` umbrella.
Scope
-----
- Adds ``account_debit`` / ``account_credit`` / ``analytic_distribution`` to
``hr.salary.rule`` (company-dependent GL mapping per rule).
- Adds ``move_id`` + ``journal_id`` + ``_fusion_create_account_move`` to
``hr.payslip``: when a payslip is validated, generates a balanced
``account.move`` from the salary rule mapping.
- Adds ``fusion_payroll_journal_id`` + ``fusion_payroll_auto_post`` to
``res.company`` (fallback journal + auto-post toggle).
- Reverse links ``payslip_ids`` / ``payslip_count`` on ``account.move``
for traceability and reporting.
Coexistence
-----------
When Odoo Enterprise's ``hr_payroll_account`` is also installed, this
module yields move-creation to it (detected at runtime via
``ir.module.module``) so payslips don't get duplicate entries. After
``hr_payroll_account`` is uninstalled, this module owns the bridge.
Auto-install
------------
Auto-installs whenever both ``hr_payroll`` and ``fusion_accounting_core``
are present.
""",
'author': 'Nexa Systems Inc.',
'website': 'https://nexasystems.ca',
'license': 'LGPL-3',
'depends': [
'fusion_accounting_core',
'account',
'hr_payroll',
'base_iban',
],
'data': [
'data/hr_salary_rule_data.xml',
'views/hr_salary_rule_views.xml',
'views/hr_payslip_views.xml',
'views/hr_payroll_structure_views.xml',
'views/res_config_settings_views.xml',
'views/account_move_views.xml',
],
'auto_install': ['hr_payroll', 'fusion_accounting_core'],
'installable': True,
'application': False,
'icon': '/fusion_accounting_hr_payroll/static/description/icon.png',
}

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!--
Bridge defaults from the Enterprise hr_payroll_account module.
Wrapped in noupdate="1" so re-running -u does not overwrite a
customer's account mapping on these rules.
Each <record> uses xmlid_lookup="ignore" through optional `forcecreate="0"`
semantics so that the load is silently skipped when the referenced
upstream rule is not present (e.g. on a database without the
Enterprise default payroll structures).
-->
<data noupdate="1">
<record id="hr_payroll.default_deduction_salary_rule" model="hr.salary.rule" forcecreate="0">
<field name="not_computed_in_net" eval="True"/>
</record>
<record id="hr_payroll.default_attachment_of_salary_rule" model="hr.salary.rule" forcecreate="0">
<field name="not_computed_in_net" eval="True"/>
</record>
<record id="hr_payroll.default_assignment_of_salary_rule" model="hr.salary.rule" forcecreate="0">
<field name="not_computed_in_net" eval="True"/>
</record>
<record id="hr_payroll.default_child_support" model="hr.salary.rule" forcecreate="0">
<field name="not_computed_in_net" eval="True"/>
</record>
<record id="hr_payroll.default_reimbursement_salary_rule" model="hr.salary.rule" forcecreate="0">
<field name="not_computed_in_net" eval="True"/>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,152 @@
# Graph Report - /Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll (2026-04-22)
## Corpus Check
- 15 files · ~4,254 words
- Verdict: corpus is large enough that graph structure adds value.
## Summary
- 55 nodes · 50 edges · 17 communities detected
- Extraction: 98% EXTRACTED · 2% INFERRED · 0% AMBIGUOUS · INFERRED: 1 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. `TestFusionPayrollBridge` - 13 edges
2. `HrPayslip` - 8 edges
3. `_fusion_enterprise_bridge_active()` - 4 edges
4. `AccountMove` - 3 edges
5. `HrPayslipRun` - 2 edges
6. `Smoke tests for the Fusion payroll bridge. Verifies that the field surface` - 1 edges
7. `HrSalaryRule` - 1 edges
8. `HrPayrollStructure` - 1 edges
9. `Pick the journal for this payslip's bridge move.` - 1 edges
10. `Pick the best partner reference for the move lines of this payslip.` - 1 edges
## Surprising Connections (you probably didn't know these)
- None detected - all connections are within the same source files.
## Communities
### Community 0 - "Community 0"
Cohesion: 0.15
Nodes (3): Smoke tests for the Fusion payroll bridge. Verifies that the field surface, TestFusionPayrollBridge, TransactionCase
### Community 1 - "Community 1"
Cohesion: 0.27
Nodes (5): HrPayslip, Build a balanced ``account.move`` from this payslip using the ``account_, Pick the journal for this payslip's bridge move., Pick the best partner reference for the move lines of this payslip., Hook so a localisation can override which payslip-line value is posted.
### Community 2 - "Community 2"
Cohesion: 0.5
Nodes (1): AccountMove
### Community 3 - "Community 3"
Cohesion: 0.5
Nodes (1): _fusion_enterprise_bridge_active()
### Community 4 - "Community 4"
Cohesion: 0.67
Nodes (1): HrPayrollStructure
### Community 5 - "Community 5"
Cohesion: 0.67
Nodes (1): HrPayslipRun
### Community 6 - "Community 6"
Cohesion: 1.0
Nodes (1): HrSalaryRule
### Community 7 - "Community 7"
Cohesion: 1.0
Nodes (1): AccountJournal
### Community 8 - "Community 8"
Cohesion: 1.0
Nodes (1): AccountMoveLine
### Community 9 - "Community 9"
Cohesion: 1.0
Nodes (1): HrPayslipLine
### Community 10 - "Community 10"
Cohesion: 1.0
Nodes (1): ResCompany
### Community 11 - "Community 11"
Cohesion: 1.0
Nodes (1): ResConfigSettings
### 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 (0):
### Community 15 - "Community 15"
Cohesion: 1.0
Nodes (0):
### Community 16 - "Community 16"
Cohesion: 1.0
Nodes (1): Return True when the Enterprise hr_payroll_account module is the authori
## Knowledge Gaps
- **13 isolated node(s):** `Smoke tests for the Fusion payroll bridge. Verifies that the field surface`, `HrSalaryRule`, `HrPayrollStructure`, `Return True when the Enterprise hr_payroll_account module is the authori`, `Pick the journal for this payslip's bridge move.` (+8 more)
These have ≤1 connection - possible missing edges or undocumented components.
- **Thin community `Community 6`** (2 nodes): `HrSalaryRule`, `hr_salary_rule.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 7`** (2 nodes): `AccountJournal`, `account_journal.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 8`** (2 nodes): `AccountMoveLine`, `account_move_line.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 9`** (2 nodes): `HrPayslipLine`, `hr_payslip_line.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 10`** (2 nodes): `ResCompany`, `res_company.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 11`** (2 nodes): `ResConfigSettings`, `res_config_settings.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): `__init__.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 14`** (1 nodes): `__init__.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 15`** (1 nodes): `__manifest__.py`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 16`** (1 nodes): `Return True when the Enterprise hr_payroll_account module is the authori`
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 `TestFusionPayrollBridge` connect `Community 0` to `Community 3`?**
_High betweenness centrality (0.172) - this node is a cross-community bridge._
- **Why does `_fusion_enterprise_bridge_active()` connect `Community 3` to `Community 1`?**
_High betweenness centrality (0.128) - this node is a cross-community bridge._
- **Why does `HrPayslip` connect `Community 1` to `Community 3`?**
_High betweenness centrality (0.067) - this node is a cross-community bridge._
- **What connects `Smoke tests for the Fusion payroll bridge. Verifies that the field surface`, `HrSalaryRule`, `HrPayrollStructure` to the rest of the system?**
_13 weakly-connected nodes found - possible documentation gaps or missing edges._

View File

@@ -0,0 +1 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_account_journal_py", "label": "account_journal.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/account_journal.py", "source_location": "L1"}, {"id": "account_journal_accountjournal", "label": "AccountJournal", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/account_journal.py", "source_location": "L4"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_account_journal_py", "target": "odoo", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/account_journal.py", "source_location": "L1", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_account_journal_py", "target": "account_journal_accountjournal", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/account_journal.py", "source_location": "L4", "weight": 1.0}], "raw_calls": []}

View File

@@ -0,0 +1 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_hr_payslip_run_py", "label": "hr_payslip_run.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/hr_payslip_run.py", "source_location": "L1"}, {"id": "hr_payslip_run_hrpaysliprun", "label": "HrPayslipRun", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/hr_payslip_run.py", "source_location": "L4"}, {"id": "hr_payslip_run_hrpaysliprun_action_open_move", "label": ".action_open_move()", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/hr_payslip_run.py", "source_location": "L19"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_hr_payslip_run_py", "target": "odoo", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/hr_payslip_run.py", "source_location": "L1", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_hr_payslip_run_py", "target": "hr_payslip_run_hrpaysliprun", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/hr_payslip_run.py", "source_location": "L4", "weight": 1.0}, {"source": "hr_payslip_run_hrpaysliprun", "target": "hr_payslip_run_hrpaysliprun_action_open_move", "relation": "method", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/hr_payslip_run.py", "source_location": "L19", "weight": 1.0}], "raw_calls": [{"caller_nid": "hr_payslip_run_hrpaysliprun_action_open_move", "callee": "ensure_one", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/hr_payslip_run.py", "source_location": "L20"}, {"caller_nid": "hr_payslip_run_hrpaysliprun_action_open_move", "callee": "_", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/hr_payslip_run.py", "source_location": "L25"}]}

View File

@@ -0,0 +1 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_hr_payslip_line_py", "label": "hr_payslip_line.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/hr_payslip_line.py", "source_location": "L1"}, {"id": "hr_payslip_line_hrpayslipline", "label": "HrPayslipLine", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/hr_payslip_line.py", "source_location": "L4"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_hr_payslip_line_py", "target": "odoo", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/hr_payslip_line.py", "source_location": "L1", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_hr_payslip_line_py", "target": "hr_payslip_line_hrpayslipline", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/hr_payslip_line.py", "source_location": "L4", "weight": 1.0}], "raw_calls": []}

View File

@@ -0,0 +1 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_hr_payroll_structure_py", "label": "hr_payroll_structure.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/hr_payroll_structure.py", "source_location": "L1"}, {"id": "hr_payroll_structure_hrpayrollstructure", "label": "HrPayrollStructure", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/hr_payroll_structure.py", "source_location": "L5"}, {"id": "hr_payroll_structure_check_journal_currency", "label": "_check_journal_currency()", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/hr_payroll_structure.py", "source_location": "L18"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_hr_payroll_structure_py", "target": "odoo", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/hr_payroll_structure.py", "source_location": "L1", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_hr_payroll_structure_py", "target": "odoo_exceptions", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/hr_payroll_structure.py", "source_location": "L2", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_hr_payroll_structure_py", "target": "hr_payroll_structure_hrpayrollstructure", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/hr_payroll_structure.py", "source_location": "L5", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_hr_payroll_structure_py", "target": "hr_payroll_structure_check_journal_currency", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/hr_payroll_structure.py", "source_location": "L18", "weight": 1.0}], "raw_calls": [{"caller_nid": "hr_payroll_structure_check_journal_currency", "callee": "sudo", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/hr_payroll_structure.py", "source_location": "L19"}, {"caller_nid": "hr_payroll_structure_check_journal_currency", "callee": "ValidationError", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/hr_payroll_structure.py", "source_location": "L23"}, {"caller_nid": "hr_payroll_structure_check_journal_currency", "callee": "_", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/hr_payroll_structure.py", "source_location": "L23"}]}

View File

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

View File

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

View File

@@ -0,0 +1 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_res_company_py", "label": "res_company.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/res_company.py", "source_location": "L1"}, {"id": "res_company_rescompany", "label": "ResCompany", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/res_company.py", "source_location": "L4"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_res_company_py", "target": "odoo", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/res_company.py", "source_location": "L1", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_res_company_py", "target": "res_company_rescompany", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/res_company.py", "source_location": "L4", "weight": 1.0}], "raw_calls": []}

View File

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

View File

@@ -0,0 +1 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_res_config_settings_py", "label": "res_config_settings.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/res_config_settings.py", "source_location": "L1"}, {"id": "res_config_settings_resconfigsettings", "label": "ResConfigSettings", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/res_config_settings.py", "source_location": "L4"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_res_config_settings_py", "target": "odoo", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/res_config_settings.py", "source_location": "L1", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_res_config_settings_py", "target": "res_config_settings_resconfigsettings", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/res_config_settings.py", "source_location": "L4", "weight": 1.0}], "raw_calls": []}

View File

@@ -0,0 +1 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_hr_salary_rule_py", "label": "hr_salary_rule.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/hr_salary_rule.py", "source_location": "L1"}, {"id": "hr_salary_rule_hrsalaryrule", "label": "HrSalaryRule", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/hr_salary_rule.py", "source_location": "L4"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_hr_salary_rule_py", "target": "odoo", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/hr_salary_rule.py", "source_location": "L1", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_hr_salary_rule_py", "target": "hr_salary_rule_hrsalaryrule", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/hr_salary_rule.py", "source_location": "L4", "weight": 1.0}], "raw_calls": []}

View File

@@ -0,0 +1 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_account_move_line_py", "label": "account_move_line.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/account_move_line.py", "source_location": "L1"}, {"id": "account_move_line_accountmoveline", "label": "AccountMoveLine", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/account_move_line.py", "source_location": "L4"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_account_move_line_py", "target": "odoo", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/account_move_line.py", "source_location": "L1", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_account_move_line_py", "target": "account_move_line_accountmoveline", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/account_move_line.py", "source_location": "L4", "weight": 1.0}], "raw_calls": []}

View File

@@ -0,0 +1 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_account_move_py", "label": "account_move.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/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_hr_payroll/models/account_move.py", "source_location": "L4"}, {"id": "account_move_accountmove_compute_payslip_count", "label": "._compute_payslip_count()", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/account_move.py", "source_location": "L20"}, {"id": "account_move_accountmove_action_open_payslip", "label": ".action_open_payslip()", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/account_move.py", "source_location": "L24"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_account_move_py", "target": "odoo", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/account_move.py", "source_location": "L1", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_account_move_py", "target": "account_move_accountmove", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/account_move.py", "source_location": "L4", "weight": 1.0}, {"source": "account_move_accountmove", "target": "account_move_accountmove_compute_payslip_count", "relation": "method", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/account_move.py", "source_location": "L20", "weight": 1.0}, {"source": "account_move_accountmove", "target": "account_move_accountmove_action_open_payslip", "relation": "method", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/account_move.py", "source_location": "L24", "weight": 1.0}], "raw_calls": [{"caller_nid": "account_move_accountmove_compute_payslip_count", "callee": "len", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/account_move.py", "source_location": "L22"}, {"caller_nid": "account_move_accountmove_action_open_payslip", "callee": "ensure_one", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/account_move.py", "source_location": "L25"}, {"caller_nid": "account_move_accountmove_action_open_payslip", "callee": "_", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/account_move.py", "source_location": "L27"}, {"caller_nid": "account_move_accountmove_action_open_payslip", "callee": "update", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/account_move.py", "source_location": "L32"}, {"caller_nid": "account_move_accountmove_action_open_payslip", "callee": "update", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/account_move.py", "source_location": "L37"}]}

View File

@@ -0,0 +1 @@
{"nodes": [{"id": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_init_py", "label": "__init__.py", "file_type": "code", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/__init__.py", "source_location": "L1"}], "edges": [{"source": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_init_py", "target": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/__init__.py", "source_location": "L1", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_init_py", "target": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/__init__.py", "source_location": "L2", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_init_py", "target": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/__init__.py", "source_location": "L3", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_init_py", "target": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/__init__.py", "source_location": "L4", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_init_py", "target": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/__init__.py", "source_location": "L5", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_init_py", "target": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/__init__.py", "source_location": "L6", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_init_py", "target": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/__init__.py", "source_location": "L7", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_init_py", "target": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/__init__.py", "source_location": "L8", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_init_py", "target": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/__init__.py", "source_location": "L9", "weight": 1.0}, {"source": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_init_py", "target": "users_gurpreet_github_odoo_modules_fusion_accounting_hr_payroll_models_init_py", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/gurpreet/Github/Odoo-Modules/fusion_accounting_hr_payroll/models/__init__.py", "source_location": "L10", "weight": 1.0}], "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,10 @@
from . import hr_salary_rule
from . import hr_payslip
from . import hr_payslip_line
from . import hr_payslip_run
from . import hr_payroll_structure
from . import account_journal
from . import account_move
from . import account_move_line
from . import res_company
from . import res_config_settings

View File

@@ -0,0 +1,12 @@
from odoo import fields, models
class AccountJournal(models.Model):
_inherit = 'account.journal'
is_payroll_journal = fields.Boolean(
string='Used for Payroll',
help="Marks this journal as the salary / payroll posting journal "
"for the company. Informational; the actual fallback is set "
"on res.company.fusion_payroll_journal_id.",
)

View File

@@ -0,0 +1,41 @@
from odoo import _, fields, models
class AccountMove(models.Model):
_inherit = 'account.move'
payslip_ids = fields.One2many(
comodel_name='hr.payslip',
inverse_name='move_id',
string='Payslips',
readonly=True,
copy=False,
)
payslip_count = fields.Integer(
string='# of Payslips',
compute='_compute_payslip_count',
compute_sudo=True,
)
def _compute_payslip_count(self):
for move in self:
move.payslip_count = len(move.payslip_ids)
def action_open_payslip(self):
self.ensure_one()
action = {
'name': _('Payslips'),
'type': 'ir.actions.act_window',
'res_model': 'hr.payslip',
}
if self.payslip_count == 1:
action.update({
'view_mode': 'form',
'res_id': self.payslip_ids.id,
})
else:
action.update({
'view_mode': 'list,form',
'domain': [('id', 'in', self.payslip_ids.ids)],
})
return action

View File

@@ -0,0 +1,16 @@
from odoo import fields, models
class AccountMoveLine(models.Model):
_inherit = 'account.move.line'
payslip_id = fields.Many2one(
'hr.payslip',
string='Source Payslip',
readonly=True,
copy=False,
ondelete='set null',
index='btree_not_null',
help="Payslip this journal item was generated from "
"(populated by the Fusion payroll bridge for reporting).",
)

View File

@@ -0,0 +1,26 @@
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
class HrPayrollStructure(models.Model):
_inherit = 'hr.payroll.structure'
journal_id = fields.Many2one(
'account.journal',
string='Salary Journal',
company_dependent=True,
domain="[('type', '=', 'general')]",
help="Default journal used when generating payroll accounting "
"entries for payslips that follow this structure.",
)
@api.constrains('journal_id')
def _check_journal_currency(self):
for record in self.sudo():
journal = record.journal_id
if journal and journal.currency_id and journal.company_id \
and journal.currency_id != journal.company_id.currency_id:
raise ValidationError(_(
"The salary journal must be in the same currency as "
"the company.",
))

View File

@@ -0,0 +1,242 @@
import logging
from collections import defaultdict
from odoo import _, api, fields, models
from odoo.exceptions import UserError
_logger = logging.getLogger(__name__)
class HrPayslip(models.Model):
_inherit = 'hr.payslip'
move_id = fields.Many2one(
'account.move',
string='Accounting Entry',
readonly=True,
copy=False,
index='btree_not_null',
)
move_state = fields.Selection(
related='move_id.state',
string='Move State',
export_string_translation=False,
)
journal_id = fields.Many2one(
'account.journal',
string='Salary Journal',
domain="[('type', '=', 'general')]",
)
@api.model
def _fusion_enterprise_bridge_active(self):
"""Return True when the Enterprise hr_payroll_account module is the
authoritative payslip - GL bridge on this database. Used to avoid
duplicate move creation while both modules coexist."""
module = self.env['ir.module.module'].sudo().search(
[('name', '=', 'hr_payroll_account')], limit=1,
)
return bool(module) and module.state == 'installed'
def _fusion_resolve_journal(self):
"""Pick the journal for this payslip's bridge move."""
self.ensure_one()
if self.journal_id:
return self.journal_id
struct = self.struct_id
if struct and 'journal_id' in struct._fields and struct.journal_id:
return struct.journal_id
company = self.company_id or self.env.company
return company.fusion_payroll_journal_id or False
def _fusion_resolve_partner(self):
"""Pick the best partner reference for the move lines of this payslip."""
self.ensure_one()
employee = self.employee_id
if not employee:
return False
if 'work_contact_id' in employee._fields and employee.work_contact_id:
return employee.work_contact_id.id
if 'address_home_id' in employee._fields and employee.address_home_id:
return employee.address_home_id.id
return False
def _fusion_get_line_amount(self, line):
"""Hook so a localisation can override which payslip-line value is
posted. Defaults to ``line.total``."""
return line.total or 0.0
def action_payslip_done(self):
res = super().action_payslip_done()
if self._fusion_enterprise_bridge_active():
return res
for slip in self:
if slip.move_id:
continue
journal = slip._fusion_resolve_journal()
if not journal:
continue
try:
slip._fusion_create_account_move(journal=journal)
except UserError as err:
_logger.warning(
"Fusion payroll bridge: GL move skipped for slip %s: %s",
slip.id, err,
)
slip.message_post(body=_(
"Fusion Payroll bridge could not create the journal "
"entry: %s",
) % err)
except Exception:
_logger.exception(
"Fusion payroll bridge: unexpected failure for slip %s",
slip.id,
)
return res
def action_payslip_cancel(self):
if hasattr(super(), 'action_payslip_cancel'):
res = super().action_payslip_cancel()
else:
res = True
if self._fusion_enterprise_bridge_active():
return res
for slip in self:
move = slip.move_id
if not move:
continue
try:
if move.state == 'posted':
move.button_draft()
move.with_context(force_delete=True).unlink()
except Exception:
_logger.exception(
"Fusion payroll bridge: cannot reverse move %s for slip %s",
move.id, slip.id,
)
return res
def _fusion_create_account_move(self, journal=None):
"""Build a balanced ``account.move`` from this payslip using the
``account_debit`` / ``account_credit`` mapping on each salary rule.
Returns the created move (or False if there is nothing to post)."""
self.ensure_one()
if not self.line_ids:
return False
journal = journal or self._fusion_resolve_journal()
if not journal:
raise UserError(_(
"No salary journal configured for company %s. "
"Set a fallback journal under Accounting Settings - "
"Fusion Payroll Bridge.",
) % (self.company_id.display_name if self.company_id else ''))
debit_per_account = defaultdict(float)
credit_per_account = defaultdict(float)
analytic_per_account = {}
for line in self.line_ids:
rule = line.salary_rule_id
amount = self._fusion_get_line_amount(line)
if not amount:
continue
debit_account = rule.account_debit
credit_account = rule.account_credit
analytic = (
rule.fusion_analytic_account_id
if 'fusion_analytic_account_id' in rule._fields
else False
)
if amount > 0:
if debit_account:
debit_per_account[debit_account.id] += amount
if credit_account:
credit_per_account[credit_account.id] += amount
else:
pos = -amount
if debit_account:
credit_per_account[debit_account.id] += pos
if credit_account:
debit_per_account[credit_account.id] += pos
if analytic:
for acc in (debit_account, credit_account):
if acc and acc.id not in analytic_per_account:
analytic_per_account[acc.id] = analytic.id
partner_id = self._fusion_resolve_partner()
line_label = self.display_name or self.number or _('Payslip')
move_lines = []
all_accounts = set(debit_per_account) | set(credit_per_account)
for account_id in all_accounts:
net = (
debit_per_account.get(account_id, 0.0)
- credit_per_account.get(account_id, 0.0)
)
if abs(net) < 0.005:
continue
vals = {
'account_id': account_id,
'name': line_label,
'partner_id': partner_id,
}
if net > 0:
vals['debit'] = round(net, 2)
vals['credit'] = 0.0
else:
vals['debit'] = 0.0
vals['credit'] = round(-net, 2)
analytic_id = analytic_per_account.get(account_id)
if analytic_id:
vals['analytic_distribution'] = {str(analytic_id): 100.0}
move_lines.append((0, 0, vals))
if not move_lines:
return False
total_debit = sum(vals[2]['debit'] for vals in move_lines)
total_credit = sum(vals[2]['credit'] for vals in move_lines)
if abs(total_debit - total_credit) > 0.01:
raise UserError(_(
"Payroll move not balanced: debit=%(d).2f, credit=%(c).2f. "
"Check the account_debit / account_credit mapping on the "
"salary rules of payslip %(name)s.",
) % {
'd': total_debit,
'c': total_credit,
'name': self.display_name,
})
move_vals = {
'journal_id': journal.id,
'date': self.date_to or fields.Date.context_today(self),
'ref': self.number or self.display_name,
'line_ids': move_lines,
'move_type': 'entry',
}
move = self.env['account.move'].sudo().create(move_vals)
if self.company_id and self.company_id.fusion_payroll_auto_post:
try:
move.action_post()
except Exception:
_logger.exception(
"Fusion payroll bridge: auto-post failed for move %s; "
"leaving in draft.",
move.id,
)
self.move_id = move.id
return move
def action_open_move(self):
self.ensure_one()
if not self.move_id:
return False
return {
'type': 'ir.actions.act_window',
'name': _('Journal Entry'),
'res_model': 'account.move',
'view_mode': 'form',
'res_id': self.move_id.id,
}

View File

@@ -0,0 +1,16 @@
from odoo import fields, models
class HrPayslipLine(models.Model):
_inherit = 'hr.payslip.line'
move_line_id = fields.Many2one(
'account.move.line',
string='Journal Item',
readonly=True,
copy=False,
ondelete='set null',
index='btree_not_null',
help="Account move line this payslip line was rolled up into "
"(set by the Fusion payroll bridge for traceability).",
)

View File

@@ -0,0 +1,29 @@
from odoo import _, fields, models
class HrPayslipRun(models.Model):
_inherit = 'hr.payslip.run'
move_id = fields.Many2one(
'account.move',
string='Batch Accounting Entry',
readonly=True,
copy=False,
ondelete='set null',
)
move_state = fields.Selection(
related='move_id.state',
string='Move State',
)
def action_open_move(self):
self.ensure_one()
if not self.move_id:
return False
return {
'type': 'ir.actions.act_window',
'name': _('Journal Entry'),
'res_model': 'account.move',
'view_mode': 'form',
'res_id': self.move_id.id,
}

View File

@@ -0,0 +1,35 @@
from odoo import fields, models
class HrSalaryRule(models.Model):
_inherit = 'hr.salary.rule'
account_debit = fields.Many2one(
'account.account',
string='Debit Account',
company_dependent=True,
ondelete='restrict',
help="GL account debited when this rule's amount is posted "
"(typically expense or asset).",
)
account_credit = fields.Many2one(
'account.account',
string='Credit Account',
company_dependent=True,
ondelete='restrict',
help="GL account credited when this rule's amount is posted "
"(typically liability).",
)
fusion_analytic_account_id = fields.Many2one(
'account.analytic.account',
string='Analytic Account',
company_dependent=True,
help="Optional analytic account applied to both legs of the move.",
)
not_computed_in_net = fields.Boolean(
string="Excluded from Net",
default=False,
help="If checked, the result of this rule is excluded from the "
"Net salary line in the journal entry. Set a dedicated "
"debit/credit account so the amount is posted independently.",
)

View File

@@ -0,0 +1,19 @@
from odoo import fields, models
class ResCompany(models.Model):
_inherit = 'res.company'
fusion_payroll_journal_id = fields.Many2one(
'account.journal',
string='Default Payroll Journal',
domain="[('type', '=', 'general'), ('company_id', '=', id)]",
help="Fallback journal used by the Fusion payroll bridge when a "
"payslip's structure does not define one.",
)
fusion_payroll_auto_post = fields.Boolean(
string='Auto-post Payroll Entries',
default=False,
help="When enabled, payroll-generated journal entries are posted "
"immediately. Otherwise they remain in draft for review.",
)

View File

@@ -0,0 +1,16 @@
from odoo import fields, models
class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
fusion_payroll_journal_id = fields.Many2one(
related='company_id.fusion_payroll_journal_id',
string='Default Payroll Journal',
readonly=False,
)
fusion_payroll_auto_post = fields.Boolean(
related='company_id.fusion_payroll_auto_post',
string='Auto-post Payroll Entries',
readonly=False,
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

View File

@@ -0,0 +1 @@
from . import test_payslip_to_move

View File

@@ -0,0 +1,108 @@
from odoo.tests import tagged
from odoo.tests.common import TransactionCase
@tagged('post_install', '-at_install')
class TestFusionPayrollBridge(TransactionCase):
"""Smoke tests for the Fusion payroll bridge.
Verifies that the field surface required to replace Enterprise's
``hr_payroll_account`` is present after the module installs.
Full payslip-to-move integration is exercised in a separate
integration test that needs a seeded payroll structure.
"""
def test_module_installed(self):
module = self.env['ir.module.module'].sudo().search(
[('name', '=', 'fusion_accounting_hr_payroll')], limit=1,
)
self.assertTrue(module, "Module record must exist")
self.assertEqual(
module.state, 'installed',
"Module should be in 'installed' state for these tests to run",
)
def test_salary_rule_has_account_fields(self):
rule_model = self.env['hr.salary.rule']
for fname in (
'account_debit',
'account_credit',
'fusion_analytic_account_id',
'not_computed_in_net',
):
self.assertIn(
fname, rule_model._fields,
f"hr.salary.rule must expose '{fname}'",
)
def test_payslip_has_move_link(self):
slip_model = self.env['hr.payslip']
for fname in ('move_id', 'move_state', 'journal_id'):
self.assertIn(
fname, slip_model._fields,
f"hr.payslip must expose '{fname}'",
)
self.assertTrue(
hasattr(slip_model, '_fusion_create_account_move'),
"hr.payslip must expose the _fusion_create_account_move bridge",
)
self.assertTrue(
hasattr(slip_model, '_fusion_enterprise_bridge_active'),
"hr.payslip must expose the Enterprise-bridge detector",
)
def test_payslip_run_has_move_link(self):
run_model = self.env['hr.payslip.run']
for fname in ('move_id', 'move_state'):
self.assertIn(
fname, run_model._fields,
f"hr.payslip.run must expose '{fname}'",
)
def test_company_payroll_journal_field(self):
co_model = self.env['res.company']
for fname in ('fusion_payroll_journal_id', 'fusion_payroll_auto_post'):
self.assertIn(
fname, co_model._fields,
f"res.company must expose '{fname}'",
)
def test_account_move_back_links(self):
move_model = self.env['account.move']
for fname in ('payslip_ids', 'payslip_count'):
self.assertIn(
fname, move_model._fields,
f"account.move must expose '{fname}'",
)
line_model = self.env['account.move.line']
self.assertIn(
'payslip_id', line_model._fields,
"account.move.line must expose 'payslip_id'",
)
def test_payslip_line_has_move_line_link(self):
line_model = self.env['hr.payslip.line']
self.assertIn(
'move_line_id', line_model._fields,
"hr.payslip.line must expose 'move_line_id'",
)
def test_enterprise_bridge_detector_returns_bool(self):
slip_model = self.env['hr.payslip']
self.assertIsInstance(
slip_model._fusion_enterprise_bridge_active(), bool,
)
def test_account_journal_payroll_flag(self):
journal_model = self.env['account.journal']
self.assertIn(
'is_payroll_journal', journal_model._fields,
"account.journal must expose 'is_payroll_journal'",
)
def test_payroll_structure_journal_field(self):
struct_model = self.env['hr.payroll.structure']
self.assertIn(
'journal_id', struct_model._fields,
"hr.payroll.structure must expose 'journal_id'",
)

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="fusion_account_move_view_form" model="ir.ui.view">
<field name="name">account.move.form.fusion.payroll.bridge</field>
<field name="model">account.move</field>
<field name="inherit_id" ref="account.view_move_form"/>
<field name="arch" type="xml">
<div name="button_box" position="inside">
<field name="payslip_count" invisible="1"/>
<button class="oe_stat_button"
name="action_open_payslip"
type="object"
icon="fa-user"
invisible="not payslip_count"
groups="hr.group_hr_user">
<div class="o_stat_info">
<field name="payslip_count" class="o_stat_value"/>
<span class="o_stat_text">Payslips</span>
</div>
</button>
</div>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="fusion_hr_payroll_structure_view_form" model="ir.ui.view">
<field name="name">hr.payroll.structure.form.fusion.payroll.bridge</field>
<field name="model">hr.payroll.structure</field>
<field name="inherit_id" ref="hr_payroll.view_hr_employee_grade_form"/>
<field name="arch" type="xml">
<xpath expr="//sheet" position="inside">
<group string="Fusion Accounting">
<field name="journal_id"/>
</group>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="fusion_hr_payslip_view_form" model="ir.ui.view">
<field name="name">hr.payslip.form.fusion.payroll.bridge</field>
<field name="model">hr.payslip</field>
<field name="inherit_id" ref="hr_payroll.view_hr_payslip_form"/>
<field name="arch" type="xml">
<div name="button_box" position="inside">
<field name="move_id" invisible="1"/>
<field name="move_state" invisible="1"/>
<button class="oe_stat_button"
name="action_open_move"
type="object"
icon="fa-bars"
invisible="not move_id"
groups="account.group_account_readonly">
<div class="o_stat_info">
<span class="o_stat_text" invisible="move_state != 'draft'">Journal Entry (Draft)</span>
<span class="o_stat_text" invisible="move_state != 'posted'">Journal Entry (Posted)</span>
<span class="o_stat_text" invisible="move_state != 'cancel'">Journal Entry (Canceled)</span>
</div>
</button>
</div>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="fusion_hr_salary_rule_view_form" model="ir.ui.view">
<field name="name">hr.salary.rule.form.fusion.payroll.bridge</field>
<field name="model">hr.salary.rule</field>
<field name="inherit_id" ref="hr_payroll.hr_salary_rule_form"/>
<field name="arch" type="xml">
<xpath expr="//sheet" position="inside">
<notebook>
<page string="Fusion Accounting" name="fusion_accounting">
<group>
<group>
<field name="account_debit" placeholder="None"/>
<field name="account_credit" placeholder="None"/>
<field name="fusion_analytic_account_id"
groups="analytic.group_analytic_accounting"
placeholder="None"/>
<field name="not_computed_in_net"/>
</group>
</group>
</page>
</notebook>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="fusion_hr_payroll_res_config_settings_view_form" model="ir.ui.view">
<field name="name">res.config.settings.form.fusion.payroll.bridge</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="account.res_config_settings_view_form"/>
<field name="arch" type="xml">
<xpath expr="//block[1]" position="before">
<block title="Fusion Payroll Bridge" id="fusion_payroll_bridge_block">
<setting id="fusion_payroll_journal_setting"
string="Default Payroll Journal"
help="Fallback journal used by the Fusion payroll bridge when a payslip's structure does not define one.">
<field name="fusion_payroll_journal_id"/>
</setting>
<setting id="fusion_payroll_auto_post_setting"
string="Auto-post Payroll Entries"
help="When enabled, payroll-generated journal entries are posted immediately. Otherwise they remain in draft for review.">
<field name="fusion_payroll_auto_post"/>
</setting>
</block>
</xpath>
</field>
</record>
</odoo>