diff --git a/payroll_account/README.rst b/payroll_account/README.rst index 98fc315a..ffcd7266 100644 --- a/payroll_account/README.rst +++ b/payroll_account/README.rst @@ -7,7 +7,7 @@ Payroll Accounting !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:653a0c1054563da937bc111ee0541f4208dd379ec0f37ba7d7d8f4fe03216a84 + !! source digest: sha256:5bdfba50457cc2a2920d5ffdf3c32cd43ab3533339bedf79c110b47f3ef95a15 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png @@ -68,6 +68,8 @@ Contributors * Odoo SA * Saran Lim. +* Moana FABER +* Cyril VINH-TUNG Maintainers ~~~~~~~~~~~ diff --git a/payroll_account/__manifest__.py b/payroll_account/__manifest__.py index 411b8b10..a11b2b6b 100644 --- a/payroll_account/__manifest__.py +++ b/payroll_account/__manifest__.py @@ -8,7 +8,10 @@ "summary": "Manage your payroll to accounting", "author": "Odoo SA, Odoo Community Association (OCA)", "depends": ["payroll", "account"], - "data": ["views/hr_payroll_account_views.xml"], + "data": [ + "views/hr_payroll_account_views.xml", + "views/res_config_settings_views.xml", + ], "demo": ["demo/hr_payroll_account_demo.xml"], "maintainers": ["appstogrow", "nimarosa"], } diff --git a/payroll_account/models/__init__.py b/payroll_account/models/__init__.py index 6c525e15..a5a0ccd2 100644 --- a/payroll_account/models/__init__.py +++ b/payroll_account/models/__init__.py @@ -1,3 +1,5 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from . import hr_payroll_account +from . import res_config_settings +from . import company diff --git a/payroll_account/models/company.py b/payroll_account/models/company.py new file mode 100644 index 00000000..5d25232a --- /dev/null +++ b/payroll_account/models/company.py @@ -0,0 +1,12 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import fields, models + + +class ResCompany(models.Model): + _inherit = "res.company" + + action_group_payslips = fields.Boolean( + string="Group payslips in accounting", + help="Allow companies to group payslips in accounting", + ) diff --git a/payroll_account/models/hr_payroll_account.py b/payroll_account/models/hr_payroll_account.py index c16d2e1f..91daf238 100644 --- a/payroll_account/models/hr_payroll_account.py +++ b/payroll_account/models/hr_payroll_account.py @@ -80,6 +80,7 @@ def onchange_contract(self): def action_payslip_cancel(self): for payslip in self: if not payslip.move_id.journal_id.restrict_mode_hash_table: + # TODO : cancel all the grouped payslips payslip.move_id.with_context(force_delete=True).button_cancel() payslip.move_id.with_context(force_delete=True).unlink() else: @@ -87,9 +88,69 @@ def action_payslip_cancel(self): payslip.move_id = False return super(HrPayslip, self).action_payslip_cancel() + def _check_can_merge(self, move_date, move_journal): + # Check that payslips can be accounted together + for slip in self: + # Check date + date = slip.date or slip.date_to + if date != move_date: + raise UserError( + _( + "Only payslips with the same date can be accounted together." + " The payslip '%s' has a different date!" + ) + % (slip.number) + ) + # Check journal + journal = slip.journal_id.id + if journal != move_journal: + raise UserError( + _( + "Only payslips with the same salary journal can be accounted together." + " The payslip '%s' has a different salary journal!" + ) + % (slip.number) + ) + + def _merge_move_lines(self, move_lines, line_ids): + for line in line_ids: + del line[2]["partner_id"] + account_in_move = False + for move_line in move_lines: + if ( + line[2]["account_id"] == move_line[2]["account_id"] + and line[2]["analytic_distribution"] + == move_line[2]["analytic_distribution"] + and line[2]["debit"] == move_line[2]["debit"] == 0 + ): + account_in_move = True + move_line[2]["credit"] += line[2]["credit"] + break + if ( + line[2]["account_id"] == move_line[2]["account_id"] + and line[2]["analytic_distribution"] + == move_line[2]["analytic_distribution"] + and line[2]["credit"] == move_line[2]["credit"] == 0 + ): + account_in_move = True + move_line[2]["debit"] += line[2]["debit"] + break + if not account_in_move: + move_lines.append(line) + return move_lines + def action_payslip_done(self): - res = super(HrPayslip, self).action_payslip_done() + # Check if we can merge payslips in accounting + if len(self) > 1 and self.env.company.action_group_payslips: + # Define account move general information based on the first slip + move_date = self[0].date or self[0].date_to + move_journal = self[0].journal_id.id + self._check_can_merge(move_date, move_journal) + # Initialize account move and account move lines + move = False + move_lines = [] + # Compute account move lines for slip in self: line_ids = [] debit_sum = 0.0 @@ -99,13 +160,6 @@ def action_payslip_done(self): slip.company_id.currency_id or slip.journal_id.company_id.currency_id ) - name = _("Payslip of %s") % (slip.employee_id.name) - move_dict = { - "narration": name, - "ref": slip.number, - "journal_id": slip.journal_id.id, - "date": date, - } for line in slip.line_ids: amount = currency.round(slip.credit_note and -line.total or line.total) if currency.is_zero(amount): @@ -273,14 +327,42 @@ def action_payslip_done(self): ) line_ids.append(adjust_debit) if len(line_ids) > 0: - move_dict["line_ids"] = line_ids - move = self.env["account.move"].create(move_dict) - slip.write({"move_id": move.id, "date": date}) - move.action_post() + if len(self) == 1 or not self.env.company.action_group_payslips: + # We do a single account move + name = _("Payslip of %s") % (slip.employee_id.name) + move_dict = { + "narration": name, + "ref": slip.number, + "journal_id": slip.journal_id.id, + "date": date, + } + move_dict["line_ids"] = line_ids + move_slip = self.env["account.move"].create(move_dict) + slip.write({"move_id": move_slip.id}) + move_slip.action_post() + + else: + # We want to merge account entries in a single account move + if not move: + move = self.env["account.move"].create({}) + move_lines = self._merge_move_lines(move_lines, line_ids) + slip.write({"move_id": move.id}) else: logger.warning( f"Payslip {slip.number} did not generate any account move lines" ) + res = super(HrPayslip, slip).action_payslip_done() + + # Add account move lines into the global account move + if len(self) > 1 and self.env.company.action_group_payslips: + move_dict = { + "narration": _("Payslips of %s") % (move_date), + "journal_id": move_journal, + "date": move_date, + } + move_dict["line_ids"] = move_lines + move.write(move_dict) + move.action_post() return res diff --git a/payroll_account/models/res_config_settings.py b/payroll_account/models/res_config_settings.py new file mode 100644 index 00000000..20e191e7 --- /dev/null +++ b/payroll_account/models/res_config_settings.py @@ -0,0 +1,15 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + action_group_payslips = fields.Boolean( + string="Group payslips in accounting", + related="company_id.action_group_payslips", + help="Allow companies to group payslips in accounting", + readonly=False, + store=True, + ) diff --git a/payroll_account/readme/CONTRIBUTORS.rst b/payroll_account/readme/CONTRIBUTORS.rst index 5de129c1..8ea542e0 100644 --- a/payroll_account/readme/CONTRIBUTORS.rst +++ b/payroll_account/readme/CONTRIBUTORS.rst @@ -1,2 +1,4 @@ * Odoo SA * Saran Lim. +* Moana FABER +* Cyril VINH-TUNG diff --git a/payroll_account/static/description/index.html b/payroll_account/static/description/index.html index 532e7953..87161aaf 100644 --- a/payroll_account/static/description/index.html +++ b/payroll_account/static/description/index.html @@ -1,3 +1,4 @@ + @@ -366,7 +367,7 @@

Payroll Accounting

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:653a0c1054563da937bc111ee0541f4208dd379ec0f37ba7d7d8f4fe03216a84 +!! source digest: sha256:5bdfba50457cc2a2920d5ffdf3c32cd43ab3533339bedf79c110b47f3ef95a15 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Beta License: LGPL-3 OCA/payroll Translate me on Weblate Try me on Runboat

Generic Payroll system Integrated with Accounting.

@@ -412,6 +413,8 @@

Contributors

diff --git a/payroll_account/views/res_config_settings_views.xml b/payroll_account/views/res_config_settings_views.xml new file mode 100644 index 00000000..3d0cf6fd --- /dev/null +++ b/payroll_account/views/res_config_settings_views.xml @@ -0,0 +1,30 @@ + + + + res.config.settings.view.form.inherit.hr.payroll.account + res.config.settings + + + +
+
+
+ +
+
+
+
+
+
+
+
+
diff --git a/payroll_account/wizard/__init__.py b/payroll_account/wizard/__init__.py index dd67996b..94e45b34 100644 --- a/payroll_account/wizard/__init__.py +++ b/payroll_account/wizard/__init__.py @@ -1,3 +1,4 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from . import hr_payroll_payslips_by_employees +from . import hr_payslip_change_state diff --git a/payroll_account/wizard/hr_payslip_change_state.py b/payroll_account/wizard/hr_payslip_change_state.py new file mode 100644 index 00000000..40100736 --- /dev/null +++ b/payroll_account/wizard/hr_payslip_change_state.py @@ -0,0 +1,35 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models +from odoo.exceptions import UserError +from odoo.tools.translate import _ + + +class HrPayslipChangeState(models.TransientModel): + + _inherit = "hr.payslip.change.state" + + def change_state_confirm(self): + company_id = self.env.company.id + config_settings = self.env["res.config.settings"].search( + [("company_id", "=", company_id)], limit=1 + ) + action_group_payslips = config_settings.action_group_payslips + + if action_group_payslips and self.state == "done": + record_ids = self.env.context.get("active_ids", False) + payslip_obj = self.env["hr.payslip"] + records = payslip_obj.browse(record_ids) + for rec in records: + if rec.state not in ("verify", "draft"): + raise UserError( + _( + "Only payslips in states verify or draft" + " can be confirmed, the payslip %(nm)s is in " + "%(st)s state" + ) + % {"nm": rec.name, "st": rec.state} + ) + records.action_payslip_done() + else: + return super(HrPayslipChangeState, self).change_state_confirm()