changes
This commit is contained in:
BIN
fusion_accounting/fusion_accounting_hr_payroll/.DS_Store
vendored
Normal file
BIN
fusion_accounting/fusion_accounting_hr_payroll/.DS_Store
vendored
Normal file
Binary file not shown.
@@ -0,0 +1 @@
|
||||
from . import models
|
||||
@@ -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',
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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._
|
||||
@@ -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": []}
|
||||
@@ -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"}]}
|
||||
File diff suppressed because one or more lines are too long
@@ -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": []}
|
||||
@@ -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"}]}
|
||||
@@ -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": []}
|
||||
@@ -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": []}
|
||||
@@ -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": []}
|
||||
@@ -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": []}
|
||||
@@ -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": []}
|
||||
@@ -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": []}
|
||||
@@ -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": []}
|
||||
@@ -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"}]}
|
||||
File diff suppressed because one or more lines are too long
@@ -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
@@ -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
|
||||
@@ -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.",
|
||||
)
|
||||
@@ -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
|
||||
@@ -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).",
|
||||
)
|
||||
@@ -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.",
|
||||
))
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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).",
|
||||
)
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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.",
|
||||
)
|
||||
@@ -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.",
|
||||
)
|
||||
@@ -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 |
@@ -0,0 +1 @@
|
||||
from . import test_payslip_to_move
|
||||
@@ -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'",
|
||||
)
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user