From 72c36898232dda58186d047256b2e7802ab05e57 Mon Sep 17 00:00:00 2001 From: Saran440 Date: Tue, 27 Jul 2021 13:23:00 +0700 Subject: [PATCH 01/75] [14.0][ADD] hr_expense_payment (cherry picked from commit 958f0483ef96ca924c91b21cebf2ebf21180f179) --- hr_expense_payment/README.rst | 82 ++++ hr_expense_payment/__init__.py | 5 + hr_expense_payment/__manifest__.py | 16 + hr_expense_payment/hooks.py | 19 + hr_expense_payment/models/__init__.py | 5 + hr_expense_payment/models/account_payment.py | 19 + hr_expense_payment/models/hr_expense.py | 63 +++ hr_expense_payment/models/hr_expense_sheet.py | 26 ++ hr_expense_payment/readme/CONTRIBUTORS.rst | 1 + hr_expense_payment/readme/DESCRIPTION.rst | 2 + .../static/description/icon.png | Bin 0 -> 9455 bytes .../static/description/index.html | 422 ++++++++++++++++++ hr_expense_payment/tests/__init__.py | 3 + .../tests/test_hr_expense_payment.py | 91 ++++ hr_expense_payment/wizard/__init__.py | 3 + .../wizard/account_payment_register.py | 30 ++ 16 files changed, 787 insertions(+) create mode 100644 hr_expense_payment/README.rst create mode 100644 hr_expense_payment/__init__.py create mode 100644 hr_expense_payment/__manifest__.py create mode 100644 hr_expense_payment/hooks.py create mode 100644 hr_expense_payment/models/__init__.py create mode 100644 hr_expense_payment/models/account_payment.py create mode 100644 hr_expense_payment/models/hr_expense.py create mode 100644 hr_expense_payment/models/hr_expense_sheet.py create mode 100644 hr_expense_payment/readme/CONTRIBUTORS.rst create mode 100644 hr_expense_payment/readme/DESCRIPTION.rst create mode 100644 hr_expense_payment/static/description/icon.png create mode 100644 hr_expense_payment/static/description/index.html create mode 100644 hr_expense_payment/tests/__init__.py create mode 100644 hr_expense_payment/tests/test_hr_expense_payment.py create mode 100644 hr_expense_payment/wizard/__init__.py create mode 100644 hr_expense_payment/wizard/account_payment_register.py diff --git a/hr_expense_payment/README.rst b/hr_expense_payment/README.rst new file mode 100644 index 000000000..b82c96946 --- /dev/null +++ b/hr_expense_payment/README.rst @@ -0,0 +1,82 @@ +================== +HR Expense Payment +================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fhr--expense-lightgray.png?logo=github + :target: https://github.com/OCA/hr-expense/tree/14.0/hr_expense_payment + :alt: OCA/hr-expense +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/hr-expense-14-0/hr-expense-14-0-hr_expense_payment + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/289/14.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allow payment link to expense. +After you register payment on expense sheet, it will link between expense sheet and payment. + +**Table of contents** + +.. contents:: + :local: + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Ecosoft + +Contributors +~~~~~~~~~~~~ + +* Saran Lim. + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-Saran440| image:: https://github.com/Saran440.png?size=40px + :target: https://github.com/Saran440 + :alt: Saran440 + +Current `maintainer `__: + +|maintainer-Saran440| + +This module is part of the `OCA/hr-expense `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/hr_expense_payment/__init__.py b/hr_expense_payment/__init__.py new file mode 100644 index 000000000..c127efb6a --- /dev/null +++ b/hr_expense_payment/__init__.py @@ -0,0 +1,5 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import models +from . import wizard +from .hooks import post_init_hook diff --git a/hr_expense_payment/__manifest__.py b/hr_expense_payment/__manifest__.py new file mode 100644 index 000000000..a0d156b95 --- /dev/null +++ b/hr_expense_payment/__manifest__.py @@ -0,0 +1,16 @@ +# Copyright 2019 Tecnativa - Ernesto Tejeda +# Copyright 2021 Ecosoft Co., Ltd (http://ecosoft.co.th/) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "HR Expense Payment", + "version": "14.0.1.0.0", + "category": "Human Resources", + "author": "Tecnativa, Ecosoft, Odoo Community Association (OCA)", + "license": "AGPL-3", + "website": "https://github.com/OCA/hr-expense", + "depends": ["hr_expense"], + "data": [], + "installable": True, + "post_init_hook": "post_init_hook", +} diff --git a/hr_expense_payment/hooks.py b/hr_expense_payment/hooks.py new file mode 100644 index 000000000..997bf6842 --- /dev/null +++ b/hr_expense_payment/hooks.py @@ -0,0 +1,19 @@ +# Copyright 2019 Tecnativa - Ernesto Tejeda +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import SUPERUSER_ID, api + + +def post_init_hook(cr, registry): + """ Trying to fill the source expense sheet in payments """ + with api.Environment.manage(): + env = api.Environment(cr, SUPERUSER_ID, {}) + sheets = env["hr.expense.sheet"].search([("payment_mode", "=", "own_account")]) + for sheet in sheets: + amls = sheet.account_move_id.mapped("line_ids") + reconcile = amls.mapped("full_reconcile_id") + aml_payment = reconcile.mapped("reconciled_line_ids").filtered( + lambda r: r not in amls + ) + payment = aml_payment.mapped("payment_id") + payment.write({"expense_sheet_ids": sheet.ids}) diff --git a/hr_expense_payment/models/__init__.py b/hr_expense_payment/models/__init__.py new file mode 100644 index 000000000..e9fa1d92d --- /dev/null +++ b/hr_expense_payment/models/__init__.py @@ -0,0 +1,5 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import account_payment +from . import hr_expense +from . import hr_expense_sheet diff --git a/hr_expense_payment/models/account_payment.py b/hr_expense_payment/models/account_payment.py new file mode 100644 index 000000000..0edcaea31 --- /dev/null +++ b/hr_expense_payment/models/account_payment.py @@ -0,0 +1,19 @@ +# Copyright 2019 Tecnativa - Ernesto Tejeda +# Copyright 2021 Ecosoft Co., Ltd (http://ecosoft.co.th/) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class AccountPayment(models.Model): + _inherit = "account.payment" + + expense_sheet_ids = fields.Many2many( + comodel_name="hr.expense.sheet", + relation="payment_expense_sheet_rel", + column1="payment_id", + column2="sheet_id", + string="Expense sheet", + readonly=True, + copy=False, + ) diff --git a/hr_expense_payment/models/hr_expense.py b/hr_expense_payment/models/hr_expense.py new file mode 100644 index 000000000..ad0830489 --- /dev/null +++ b/hr_expense_payment/models/hr_expense.py @@ -0,0 +1,63 @@ +# Copyright 2019 Tecnativa - Ernesto Tejeda +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import models + + +class HrExpense(models.Model): + _inherit = "hr.expense" + + def _prepare_payment_expense_company( + self, + payment_methods, + journal, + different_currency, + journal_currency, + total_amount_currency, + ): + self.ensure_one() + commercial_partner_id = ( + self.employee_id.sudo().address_home_id.commercial_partner_id + ) + payment_dict = { + "date": self.date, + "amount": total_amount_currency, + "payment_type": "outbound", + "partner_type": "supplier", + "ref": self.name, + "journal_id": journal.id, + "currency_id": self.currency_id.id + if different_currency + else journal_currency.id, + "partner_id": commercial_partner_id.id, + "payment_method_id": payment_methods and payment_methods[0].id or False, + "expense_sheet_ids": self.sheet_id.ids, + } + return payment_dict + + def action_move_create(self): + move_group_by_sheet = super().action_move_create() + payment_list = [] + for expense in self: + if expense.payment_mode == "company_account": + total_amount_currency = expense.total_amount + different_currency = ( + expense.currency_id != expense.company_id.currency_id + ) + journal = expense.sheet_id.bank_journal_id + journal_currency = journal.currency_id or journal.company_id.currency_id + payment_methods = journal.outbound_payment_method_ids + # prepare payment dict value for case paid by company + payment_dict = expense._prepare_payment_expense_company( + payment_methods, + journal, + different_currency, + journal_currency, + total_amount_currency, + ) + payment_list.append(payment_dict) + # create payment and auto post from expense paid by company + if payment_list: + payment = self.env["account.payment"].create(payment_list) + payment.action_post() + return move_group_by_sheet diff --git a/hr_expense_payment/models/hr_expense_sheet.py b/hr_expense_payment/models/hr_expense_sheet.py new file mode 100644 index 000000000..4acab3067 --- /dev/null +++ b/hr_expense_payment/models/hr_expense_sheet.py @@ -0,0 +1,26 @@ +# Copyright 2019 Tecnativa - Ernesto Tejeda +# Copyright 2021 Ecosoft Co., Ltd (http://ecosoft.co.th/) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class HrExpenseSheet(models.Model): + _inherit = "hr.expense.sheet" + + payment_ids = fields.Many2many( + comodel_name="account.payment", + relation="payment_expense_sheet_rel", + column1="sheet_id", + column2="payment_id", + string="Payment", + readonly=True, + copy=False, + ) + + def action_register_payment(self): + """ Send context when you register payment from expense sheet """ + action = super().action_register_payment() + if self._name == "hr.expense.sheet": + action["context"].update({"expense_sheet_ids": self.ids}) + return action diff --git a/hr_expense_payment/readme/CONTRIBUTORS.rst b/hr_expense_payment/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..cc6b23102 --- /dev/null +++ b/hr_expense_payment/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Saran Lim. diff --git a/hr_expense_payment/readme/DESCRIPTION.rst b/hr_expense_payment/readme/DESCRIPTION.rst new file mode 100644 index 000000000..2bfb486f3 --- /dev/null +++ b/hr_expense_payment/readme/DESCRIPTION.rst @@ -0,0 +1,2 @@ +This module allow payment link to expense. +After you register payment on expense sheet, it will link between expense sheet and payment. diff --git a/hr_expense_payment/static/description/icon.png b/hr_expense_payment/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 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 z 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 Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 diff --git a/hr_expense_payment/static/description/index.html b/hr_expense_payment/static/description/index.html new file mode 100644 index 000000000..e6349f8e0 --- /dev/null +++ b/hr_expense_payment/static/description/index.html @@ -0,0 +1,422 @@ + + + + + + +HR Expense Payment + + + +
+

HR Expense Payment

+ + +

Beta License: AGPL-3 OCA/hr-expense Translate me on Weblate Try me on Runbot

+

This module allow payment link to expense. +After you register payment on expense sheet, it will link between expense sheet and payment.

+

Table of contents

+ +
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Ecosoft
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

Saran440

+

This module is part of the OCA/hr-expense project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/hr_expense_payment/tests/__init__.py b/hr_expense_payment/tests/__init__.py new file mode 100644 index 000000000..36579e552 --- /dev/null +++ b/hr_expense_payment/tests/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from . import test_hr_expense_payment diff --git a/hr_expense_payment/tests/test_hr_expense_payment.py b/hr_expense_payment/tests/test_hr_expense_payment.py new file mode 100644 index 000000000..b24ae55c3 --- /dev/null +++ b/hr_expense_payment/tests/test_hr_expense_payment.py @@ -0,0 +1,91 @@ +# Copyright 2019 Tecnativa - Ernesto Tejeda +# Copyright 2021 Ecosoft Co., Ltd (http://ecosoft.co.th/) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo.tests.common import Form, TransactionCase + +from ..hooks import post_init_hook + + +class TestHrExpensePayment(TransactionCase): + def setUp(self): + super().setUp() + self.account_payment_register = self.env["account.payment.register"] + self.payment_journal = self.env["account.journal"].search( + [("type", "in", ["cash", "bank"])], limit=1 + ) + + company = self.env.ref("base.main_company") + self.expense_journal = self.env["account.journal"].create( + { + "name": "Purchase Journal - Test", + "code": "HRTPJ", + "type": "purchase", + "company_id": company.id, + } + ) + + self.expense_sheet = self.env["hr.expense.sheet"].create( + { + "employee_id": self.ref("hr.employee_admin"), + "name": "Expense test", + "journal_id": self.expense_journal.id, + } + ) + self.expense_sheet.approve_expense_sheets() + + self.expense = self.env["hr.expense"].create( + { + "name": "Expense test", + "employee_id": self.ref("hr.employee_admin"), + "product_id": self.ref("hr_expense.air_ticket"), + "unit_amount": 1, + "quantity": 10, + "sheet_id": self.expense_sheet.id, + } + ) + + def _get_payment_wizard(self, expense_sheet): + action = expense_sheet.action_register_payment() + ctx = action.get("context") + with Form( + self.account_payment_register.with_context(ctx), + view="account.view_account_payment_register_form", + ) as f: + f.journal_id = self.payment_journal + f.amount = self.expense_sheet.total_amount + register_payment = f.save() + return register_payment + + def test_post_init_hook(self): + self.expense_sheet.action_sheet_move_create() + payment_wizard = self._get_payment_wizard(self.expense_sheet) + payment_wizard.action_create_payments() + + payment = self.expense_sheet.payment_ids + + self.assertEqual(len(payment), 1) + self.assertEqual(len(payment.expense_sheet_ids), 1) + + payment.expense_sheet_ids = False + # Recompute many2one + payment = self.expense_sheet.payment_ids + + self.assertFalse(payment) + self.assertFalse(payment.expense_sheet_ids) + post_init_hook(self.env.cr, self.registry) + + self.assertEqual(len(self.expense_sheet.payment_ids), 1) + + def test_get_payment_vals(self): + self.expense_sheet.action_sheet_move_create() + payment_wizard = self._get_payment_wizard(self.expense_sheet) + self.assertFalse(self.expense_sheet.payment_ids) + payment_wizard.action_create_payments() + self.assertEqual(len(self.expense_sheet.payment_ids), 1) + + def test_action_sheet_move_create(self): + self.expense.payment_mode = "company_account" + self.assertFalse(self.expense_sheet.payment_ids) + self.expense_sheet.action_sheet_move_create() + self.assertEqual(len(self.expense_sheet.payment_ids), 1) diff --git a/hr_expense_payment/wizard/__init__.py b/hr_expense_payment/wizard/__init__.py new file mode 100644 index 000000000..09e22cb07 --- /dev/null +++ b/hr_expense_payment/wizard/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import account_payment_register diff --git a/hr_expense_payment/wizard/account_payment_register.py b/hr_expense_payment/wizard/account_payment_register.py new file mode 100644 index 000000000..dc4f40e1f --- /dev/null +++ b/hr_expense_payment/wizard/account_payment_register.py @@ -0,0 +1,30 @@ +# Copyright 2019 Tecnativa - Ernesto Tejeda +# Copyright 2020 Ecosoft Co., Ltd (https://ecosoft.co.th/) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import models + + +class AccountPaymentRegister(models.TransientModel): + _inherit = "account.payment.register" + + def _create_payment_vals_from_wizard(self): + payment_vals = super()._create_payment_vals_from_wizard() + expense_sheet_ids = self._context.get("expense_sheet_ids", False) + if expense_sheet_ids: + payment_vals.update(expense_sheet_ids=expense_sheet_ids) + return payment_vals + + def _create_payment_vals_from_batch(self, batch_result): + payment_vals = super()._create_payment_vals_from_batch(batch_result) + expense_sheet_ids = self._context.get("expense_sheet_ids", False) + if expense_sheet_ids: + move_line_ids = self.env["account.move.line"].browse( + batch_result["lines"].ids + ) + sheet_ids = self.env["hr.expense.sheet"].browse(expense_sheet_ids) + sheet_id = sheet_ids.filtered( + lambda l: l.account_move_id.id == move_line_ids.move_id.id + ) + payment_vals.update(expense_sheet_ids=sheet_id.ids) + return payment_vals From e221478e34e01d74ffc2aa6eceed8ea63387bb1d Mon Sep 17 00:00:00 2001 From: Saran440 Date: Tue, 27 Jul 2021 13:28:28 +0700 Subject: [PATCH 02/75] [ENH] hr_expense_cancel depend on hr_expense_payment (cherry picked from commit 1fb90c168ca022fc6f027c04ae41f68821148d07) --- hr_expense_payment/models/hr_expense.py | 3 +++ hr_expense_payment/tests/test_hr_expense_payment.py | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/hr_expense_payment/models/hr_expense.py b/hr_expense_payment/models/hr_expense.py index ad0830489..d80514042 100644 --- a/hr_expense_payment/models/hr_expense.py +++ b/hr_expense_payment/models/hr_expense.py @@ -38,6 +38,9 @@ def _prepare_payment_expense_company( def action_move_create(self): move_group_by_sheet = super().action_move_create() payment_list = [] + # you can skip create payment from expense paid by company_account + if self.env.context.get("skip_create_payment_company_account", False): + return move_group_by_sheet for expense in self: if expense.payment_mode == "company_account": total_amount_currency = expense.total_amount diff --git a/hr_expense_payment/tests/test_hr_expense_payment.py b/hr_expense_payment/tests/test_hr_expense_payment.py index b24ae55c3..c1ffc9c75 100644 --- a/hr_expense_payment/tests/test_hr_expense_payment.py +++ b/hr_expense_payment/tests/test_hr_expense_payment.py @@ -89,3 +89,12 @@ def test_action_sheet_move_create(self): self.assertFalse(self.expense_sheet.payment_ids) self.expense_sheet.action_sheet_move_create() self.assertEqual(len(self.expense_sheet.payment_ids), 1) + + def test_action_sheet_move_create_skip_payment(self): + self.expense.payment_mode = "company_account" + self.assertFalse(self.expense_sheet.payment_ids) + self.expense_sheet.with_context( + skip_create_payment_company_account=True + ).action_sheet_move_create() + self.assertFalse(self.expense_sheet.payment_ids) + self.assertEqual(self.expense_sheet.state, "done") From 1b8b98afb723b9cd7e7d59dbab93eb0333af51bd Mon Sep 17 00:00:00 2001 From: oca-travis Date: Sat, 5 Mar 2022 05:21:41 +0000 Subject: [PATCH 03/75] [UPD] Update hr_expense_payment.pot (cherry picked from commit 3117aaf5ceeb27ee8722aababaf4574d5ed8e1d0) --- .../i18n/hr_expense_payment.pot | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 hr_expense_payment/i18n/hr_expense_payment.pot diff --git a/hr_expense_payment/i18n/hr_expense_payment.pot b/hr_expense_payment/i18n/hr_expense_payment.pot new file mode 100644 index 000000000..a7d848668 --- /dev/null +++ b/hr_expense_payment/i18n/hr_expense_payment.pot @@ -0,0 +1,68 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * hr_expense_payment +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: hr_expense_payment +#: model:ir.model.fields,field_description:hr_expense_payment.field_account_payment__display_name +#: model:ir.model.fields,field_description:hr_expense_payment.field_account_payment_register__display_name +#: model:ir.model.fields,field_description:hr_expense_payment.field_hr_expense__display_name +#: model:ir.model.fields,field_description:hr_expense_payment.field_hr_expense_sheet__display_name +msgid "Display Name" +msgstr "" + +#. module: hr_expense_payment +#: model:ir.model,name:hr_expense_payment.model_hr_expense +msgid "Expense" +msgstr "" + +#. module: hr_expense_payment +#: model:ir.model,name:hr_expense_payment.model_hr_expense_sheet +msgid "Expense Report" +msgstr "" + +#. module: hr_expense_payment +#: model:ir.model.fields,field_description:hr_expense_payment.field_account_payment__expense_sheet_ids +msgid "Expense sheet" +msgstr "" + +#. module: hr_expense_payment +#: model:ir.model.fields,field_description:hr_expense_payment.field_account_payment__id +#: model:ir.model.fields,field_description:hr_expense_payment.field_account_payment_register__id +#: model:ir.model.fields,field_description:hr_expense_payment.field_hr_expense__id +#: model:ir.model.fields,field_description:hr_expense_payment.field_hr_expense_sheet__id +msgid "ID" +msgstr "" + +#. module: hr_expense_payment +#: model:ir.model.fields,field_description:hr_expense_payment.field_account_payment____last_update +#: model:ir.model.fields,field_description:hr_expense_payment.field_account_payment_register____last_update +#: model:ir.model.fields,field_description:hr_expense_payment.field_hr_expense____last_update +#: model:ir.model.fields,field_description:hr_expense_payment.field_hr_expense_sheet____last_update +msgid "Last Modified on" +msgstr "" + +#. module: hr_expense_payment +#: model:ir.model.fields,field_description:hr_expense_payment.field_hr_expense_sheet__payment_ids +msgid "Payment" +msgstr "" + +#. module: hr_expense_payment +#: model:ir.model,name:hr_expense_payment.model_account_payment +msgid "Payments" +msgstr "" + +#. module: hr_expense_payment +#: model:ir.model,name:hr_expense_payment.model_account_payment_register +msgid "Register Payment" +msgstr "" From 3a2b58b5731b9510677b92c22cab1dbc7889babb Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Sat, 5 Mar 2022 05:26:07 +0000 Subject: [PATCH 04/75] [UPD] README.rst (cherry picked from commit 8af8d1d51c569e7a055ea8e099e81c07658eab4f) --- hr_expense_payment/README.rst | 9 +-------- hr_expense_payment/static/description/index.html | 5 ++--- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/hr_expense_payment/README.rst b/hr_expense_payment/README.rst index b82c96946..d15fae650 100644 --- a/hr_expense_payment/README.rst +++ b/hr_expense_payment/README.rst @@ -49,6 +49,7 @@ Credits Authors ~~~~~~~ +* Tecnativa * Ecosoft Contributors @@ -69,14 +70,6 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use. -.. |maintainer-Saran440| image:: https://github.com/Saran440.png?size=40px - :target: https://github.com/Saran440 - :alt: Saran440 - -Current `maintainer `__: - -|maintainer-Saran440| - This module is part of the `OCA/hr-expense `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/hr_expense_payment/static/description/index.html b/hr_expense_payment/static/description/index.html index e6349f8e0..8fb2e7e56 100644 --- a/hr_expense_payment/static/description/index.html +++ b/hr_expense_payment/static/description/index.html @@ -3,7 +3,7 @@ - + HR Expense Payment -
-

HR Expense Payment

+
+ + +Odoo Community Association + +
+

HR Expense Payment

-

Beta License: AGPL-3 OCA/hr-expense Translate me on Weblate Try me on Runboat

-

This module allow payment link to expense. After you register payment on -expense sheet, it will link between expense sheet and payment.

+

Beta License: AGPL-3 OCA/hr-expense Translate me on Weblate Try me on Runboat

+

This module records a bidirectional link between each hr.expense +record and the account.payment records that paid it (via +hr.expense.payment_ids and account.payment.expense_ids).

+

When a user clicks Register Payment on a posted employee-paid +expense (payment_mode='own_account'), the resulting payment +back-links to the source expense. A post_init_hook backfills the +link for payments that predate the module install by walking +reconciliation chains.

+

19.0 note: Odoo core’s hr.expense.action_pay() already exists and +launches the standard register-payment wizard. This module adds the +back-link — core doesn’t track which payment(s) paid which expense +beyond the implicit reconciliation graph.

Table of contents

    @@ -385,24 +400,24 @@

    HR Expense Payment

-

Bug Tracker

+

Bug Tracker

Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed -feedback.

+feedback.

Do not contact contributors directly about support or help with technical issues.

-

Credits

+

Credits

-

Authors

+

Authors

  • Tecnativa
  • Ecosoft
-

Contributors

+

Contributors

-

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association @@ -421,10 +436,13 @@

Maintainers

OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.

-

This module is part of the OCA/hr-expense project on GitHub.

+

Current maintainer:

+

dnplkndll

+

This module is part of the OCA/hr-expense project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
From 34972fa63d4ee9fb465d257d94248794f4d228e5 Mon Sep 17 00:00:00 2001 From: Kitti U Date: Fri, 26 Apr 2019 18:23:22 +0700 Subject: [PATCH 32/75] [12.0][ADD] hr_expense_advance_clearing [UPD] Update hr_expense_advance_clearing.pot [UPD] README.rst (cherry picked from commit 47c739874bff9659e9580f528c5b1470f61a0e81) --- hr_expense_advance_clearing/README.rst | 138 +++++ hr_expense_advance_clearing/__init__.py | 2 + hr_expense_advance_clearing/__manifest__.py | 20 + .../data/advance_product.xml | 12 + .../i18n/hr_expense_advance_clearing.pot | 179 +++++++ .../models/__init__.py | 3 + .../models/account_payment.py | 16 + .../models/hr_expense.py | 81 +++ .../models/hr_expense_sheet.py | 111 ++++ .../readme/CONFIGURE.rst | 10 + .../readme/CONTRIBUTORS.rst | 1 + .../readme/DESCRIPTION.rst | 11 + .../readme/INSTALL.rst | 1 + hr_expense_advance_clearing/readme/USAGE.rst | 32 ++ .../static/description/icon.png | Bin 0 -> 9455 bytes .../static/description/index.html | 487 ++++++++++++++++++ hr_expense_advance_clearing/tests/__init__.py | 1 + .../tests/test_hr_expense_advance_clearing.py | 205 ++++++++ .../views/hr_expense_views.xml | 70 +++ .../wizard/__init__.py | 1 + .../hr_expense_sheet_register_payment.py | 65 +++ 21 files changed, 1446 insertions(+) create mode 100644 hr_expense_advance_clearing/README.rst create mode 100644 hr_expense_advance_clearing/__init__.py create mode 100644 hr_expense_advance_clearing/__manifest__.py create mode 100644 hr_expense_advance_clearing/data/advance_product.xml create mode 100644 hr_expense_advance_clearing/i18n/hr_expense_advance_clearing.pot create mode 100644 hr_expense_advance_clearing/models/__init__.py create mode 100644 hr_expense_advance_clearing/models/account_payment.py create mode 100644 hr_expense_advance_clearing/models/hr_expense.py create mode 100644 hr_expense_advance_clearing/models/hr_expense_sheet.py create mode 100644 hr_expense_advance_clearing/readme/CONFIGURE.rst create mode 100644 hr_expense_advance_clearing/readme/CONTRIBUTORS.rst create mode 100644 hr_expense_advance_clearing/readme/DESCRIPTION.rst create mode 100644 hr_expense_advance_clearing/readme/INSTALL.rst create mode 100644 hr_expense_advance_clearing/readme/USAGE.rst create mode 100644 hr_expense_advance_clearing/static/description/icon.png create mode 100644 hr_expense_advance_clearing/static/description/index.html create mode 100644 hr_expense_advance_clearing/tests/__init__.py create mode 100644 hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py create mode 100644 hr_expense_advance_clearing/views/hr_expense_views.xml create mode 100644 hr_expense_advance_clearing/wizard/__init__.py create mode 100644 hr_expense_advance_clearing/wizard/hr_expense_sheet_register_payment.py diff --git a/hr_expense_advance_clearing/README.rst b/hr_expense_advance_clearing/README.rst new file mode 100644 index 000000000..35059b9fd --- /dev/null +++ b/hr_expense_advance_clearing/README.rst @@ -0,0 +1,138 @@ +============================= +Employee Advance and Clearing +============================= + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fhr-lightgray.png?logo=github + :target: https://github.com/OCA/hr/tree/12.0/hr_expense_advance_clearing + :alt: OCA/hr +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/hr-12-0/hr-12-0-hr_expense_advance_clearing + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/116/12.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +Standard Expenses module allow employee to do the expense reimbursement only after the expense has been made. +In other world, employee will need to pay first and reimburse later. + +This module, allow company to advance an amount to the employee. +Employee can then use that advance amount to purchase product/service first, then back to company and do the clearing. + +There can be 3 scenarios for advance and clearing + +* When clearing amount = advance amount, no other operation is required. +* When clearing amount > advance amount, company will pay the extra to employee. +* When clearing amount < advance amount, employee will return the remain to company. + +**Table of contents** + +.. contents:: + :local: + +Installation +============ + +* No specific installation required + +Configuration +============= + +This module will create a new product "Employee Advance" automatically. +You will need to setup the Expense Account of this product to your Employee Advance account manually. + +* Open Product window and search for "Employee Advance" +* On Accounting tab, select appropriate employee advance account from your chart of account + +Note: + +* You will need the "Show Full Accounting Features" to see accounting data +* Employee Advance account code, if not already exists, you can create one. Use type = Current Asset and check Allow Reconciliation. + +Usage +===== + +**Create an Employee Advance** + +#. Go to Expenses > My Expenses +#. Create a new Expense as normal, but also check "Employee Advance" checkbox +#. Product = Employee Advance will be set automatically, do not change. +#. Set the unit price to advance amount +#. Click Create Report as normal. Please note that, this expense report will also has flag "Employee Advance" checked. +#. As normal, do Submit to Manager > Approve > Post Journal Entries > Register Payment. +#. As this is Advance, you will see new field "Amount to clear". + +**Clear Advance** + +#. Go to Expenses > My Expense Report. +#. Search for the Advance you want to clear, or use filter "Advance (not cleared)" to see all uncleared advance. +#. Open an Advance which is now in paid status with some Amount to be cleared. +#. Click button "Clear Advance", system will create new Expense Report with reference to the previous step Advance. +#. Add or create Expense line(s) as normal. +#. As normal, do Submit to Manager > Approve > Post Journal Entries + +Note: + +* If the total expense amount less than or equal to the advance amount, the status will be set to Paid right after post journal entries. +* If the total expense amount more than the advance amount, Register Payment will pay the extra amount then set state to Paid. + +**Return Advance** + +#. Go to Expenses > My Expense Report. +#. Search for the Advance you want to clear, or use filter "Advance (not cleared)" to see all uncleared advance. +#. Open an Advance which is now in paid status with some Amount to be cleared. +#. Click button "Return Advance" will open Register Payment wizard with Amount to clear. +#. Click button "Validate" to return that amount back +#. All returned, Amount to clear is now equal to 0.0 + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Ecosoft + +Contributors +~~~~~~~~~~~~ + +* Kitti Upariphutthiphong + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/hr `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/hr_expense_advance_clearing/__init__.py b/hr_expense_advance_clearing/__init__.py new file mode 100644 index 000000000..9b4296142 --- /dev/null +++ b/hr_expense_advance_clearing/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizard diff --git a/hr_expense_advance_clearing/__manifest__.py b/hr_expense_advance_clearing/__manifest__.py new file mode 100644 index 000000000..ffceed8a1 --- /dev/null +++ b/hr_expense_advance_clearing/__manifest__.py @@ -0,0 +1,20 @@ +# Copyright 2019 Kitti Upariphutthiphong +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + 'name': 'Employee Advance and Clearing', + 'version': '12.0.1.0.0', + 'category': 'Human Resources', + 'author': 'Ecosoft, ' + 'Odoo Community Association (OCA)', + 'license': 'AGPL-3', + 'website': 'https://github.com/OCA/hr', + 'depends': [ + 'hr_expense', + ], + 'data': [ + 'data/advance_product.xml', + 'views/hr_expense_views.xml', + ], + 'installable': True, +} diff --git a/hr_expense_advance_clearing/data/advance_product.xml b/hr_expense_advance_clearing/data/advance_product.xml new file mode 100644 index 000000000..3784ef122 --- /dev/null +++ b/hr_expense_advance_clearing/data/advance_product.xml @@ -0,0 +1,12 @@ + + + Employee Advance + + service + + + + False + False + + diff --git a/hr_expense_advance_clearing/i18n/hr_expense_advance_clearing.pot b/hr_expense_advance_clearing/i18n/hr_expense_advance_clearing.pot new file mode 100644 index 000000000..0c4a1a606 --- /dev/null +++ b/hr_expense_advance_clearing/i18n/hr_expense_advance_clearing.pot @@ -0,0 +1,179 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * hr_expense_advance_clearing +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: hr_expense_advance_clearing +#: code:addons/hr_expense_advance_clearing/wizard/hr_expense_sheet_register_payment.py:46 +#, python-format +msgid "A remaining advance return of %s %s with the reference %s related to your expense %s has been made." +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_filter +msgid "Advance" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_filter +msgid "Advance (not cleared)" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.actions.act_window,name:hr_expense_advance_clearing.action_hr_expense_sheet_advance_clearing +msgid "Advance Clearing" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_residual +msgid "Advance Remaining" +msgstr "" + +#. module: hr_expense_advance_clearing +#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:60 +#, python-format +msgid "Advance clearing must not contain any advance expense line" +msgstr "" + +#. module: hr_expense_advance_clearing +#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:63 +#, python-format +msgid "Advance must contain only 1 advance expense line" +msgstr "" + +#. module: hr_expense_advance_clearing +#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:55 +#, python-format +msgid "Advance: %s has no amount to clear" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__residual +msgid "Amount to clear" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__residual +msgid "Amount to clear of this expense sheet in company currency" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_id +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form +msgid "Clear Advance" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_filter +msgid "Clearing" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_hr_expense_sheet_advance_clearing +msgid "Create a new expense report" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense__advance +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__advance +#: model:product.product,name:hr_expense_advance_clearing.product_emp_advance +#: model:product.template,name:hr_expense_advance_clearing.product_emp_advance_product_template +msgid "Employee Advance" +msgstr "" + +#. module: hr_expense_advance_clearing +#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:24 +#, python-format +msgid "Employee advance product has no payable account" +msgstr "" + +#. module: hr_expense_advance_clearing +#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:30 +#, python-format +msgid "Employee advance, all taxes must be removed" +msgstr "" + +#. module: hr_expense_advance_clearing +#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:33 +#, python-format +msgid "Employee advance, paid by must be employee" +msgstr "" + +#. module: hr_expense_advance_clearing +#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:27 +#, python-format +msgid "Employee advance, selected product is not valid" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model,name:hr_expense_advance_clearing.model_hr_expense +msgid "Expense" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model,name:hr_expense_advance_clearing.model_hr_expense_sheet_register_payment_wizard +msgid "Expense Register Payment Wizard" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model,name:hr_expense_advance_clearing.model_hr_expense_sheet +msgid "Expense Report" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__amount_payable +msgid "Final regiter payment amount even after advance clearing" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_hr_expense_sheet_advance_clearing +msgid "Once you have created your expense, submit it to your manager who will validate it." +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__amount_payable +msgid "Payable Amount" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model,name:hr_expense_advance_clearing.model_account_payment +msgid "Payments" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_residual +msgid "Remaining amount to clear the selected advance sheet" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form +msgid "Return Advance" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_id +msgid "Show remaining advance of this employee" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:product.product,uom_name:hr_expense_advance_clearing.product_emp_advance +#: model:product.template,uom_name:hr_expense_advance_clearing.product_emp_advance_product_template +msgid "Unit(s)" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:product.product,weight_uom_name:hr_expense_advance_clearing.product_emp_advance +#: model:product.template,weight_uom_name:hr_expense_advance_clearing.product_emp_advance_product_template +msgid "kg" +msgstr "" + diff --git a/hr_expense_advance_clearing/models/__init__.py b/hr_expense_advance_clearing/models/__init__.py new file mode 100644 index 000000000..01f905ef5 --- /dev/null +++ b/hr_expense_advance_clearing/models/__init__.py @@ -0,0 +1,3 @@ +from . import hr_expense +from . import hr_expense_sheet +from . import account_payment diff --git a/hr_expense_advance_clearing/models/account_payment.py b/hr_expense_advance_clearing/models/account_payment.py new file mode 100644 index 000000000..165563790 --- /dev/null +++ b/hr_expense_advance_clearing/models/account_payment.py @@ -0,0 +1,16 @@ +# Copyright 2019 Kitti Upariphutthiphong +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import models + + +class AccountPayment(models.Model): + _inherit = 'account.payment' + + def _get_counterpart_move_line_vals(self, invoice=False): + emp_advance = self.env.ref('hr_expense_advance_clearing.' + 'product_emp_advance') + res = super()._get_counterpart_move_line_vals(invoice=invoice) + if self._context.get('hr_return_advance', False): + res['account_id'] = emp_advance.property_account_expense_id.id + return res diff --git a/hr_expense_advance_clearing/models/hr_expense.py b/hr_expense_advance_clearing/models/hr_expense.py new file mode 100644 index 000000000..58a9ab8ff --- /dev/null +++ b/hr_expense_advance_clearing/models/hr_expense.py @@ -0,0 +1,81 @@ +# Copyright 2019 Kitti Upariphutthiphong +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models, _ +from odoo.exceptions import ValidationError + + +class HrExpense(models.Model): + _inherit = 'hr.expense' + + advance = fields.Boolean( + string='Employee Advance', + default=False, + ) + + @api.multi + @api.constrains('advance') + def _check_advance(self): + for expense in self.filtered('advance'): + emp_advance = self.env.ref('hr_expense_advance_clearing.' + 'product_emp_advance') + if not emp_advance.property_account_expense_id: + raise ValidationError( + _('Employee advance product has no payable account')) + if expense.product_id != emp_advance: + raise ValidationError( + _('Employee advance, selected product is not valid')) + if expense.tax_ids: + raise ValidationError( + _('Employee advance, all taxes must be removed')) + if expense.payment_mode != 'own_account': + raise ValidationError( + _('Employee advance, paid by must be employee')) + return True + + @api.onchange('advance') + def onchange_advance(self): + self.tax_ids = False + self.payment_mode = 'own_account' + if self.advance: + self.product_id = self.env.ref( + 'hr_expense_advance_clearing.product_emp_advance') + else: + self.product_id = False + + @api.multi + def _get_account_move_line_values(self): + move_line_values_by_expense = super()._get_account_move_line_values() + # Only when do the clearing, change cr payable to cr advance + emp_advance = self.env.ref('hr_expense_advance_clearing.' + 'product_emp_advance') + sheets = self.mapped('sheet_id').filtered('advance_sheet_id') + sheets_x = sheets.filtered(lambda x: x.advance_sheet_residual <= 0.0) + if sheets_x: # Advance Sheets with no residual left + raise ValidationError(_('Advance: %s has no amount to clear') % + ', '.join(sheets_x.mapped('name'))) + for sheet in sheets: + advance_to_clear = sheet.advance_sheet_residual + for expense_id, move_lines in move_line_values_by_expense.items(): + payable_move_line = False + for move_line in move_lines: + credit = move_line['credit'] + if not credit: + continue + # cr payable -> cr advance + remain_payable = 0.0 + if credit > advance_to_clear: + remain_payable = credit - advance_to_clear + move_line['credit'] = advance_to_clear + advance_to_clear = 0.0 + # extra payable line + payable_move_line = move_line.copy() + payable_move_line['credit'] = remain_payable + else: + advance_to_clear -= credit + # advance line + move_line['account_id'] = \ + emp_advance.property_account_expense_id.id + if payable_move_line: + move_lines.append(payable_move_line) + return move_line_values_by_expense diff --git a/hr_expense_advance_clearing/models/hr_expense_sheet.py b/hr_expense_advance_clearing/models/hr_expense_sheet.py new file mode 100644 index 000000000..47585a110 --- /dev/null +++ b/hr_expense_advance_clearing/models/hr_expense_sheet.py @@ -0,0 +1,111 @@ +# Copyright 2019 Kitti Upariphutthiphong +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, models, fields, _ +from odoo.tools.safe_eval import safe_eval +from odoo.tools import pycompat +from odoo.exceptions import ValidationError + + +class HrExpenseSheet(models.Model): + _inherit = 'hr.expense.sheet' + + advance = fields.Boolean( + string='Employee Advance', + compute='_compute_advance', + store=True, + ) + advance_sheet_id = fields.Many2one( + comodel_name='hr.expense.sheet', + string='Clear Advance', + domain="[('advance', '=', True), ('employee_id', '=', employee_id)," + " ('residual', '>', 0.0)]", + readonly=True, + states={'draft': [('readonly', False)], + 'submit': [('readonly', False)], + 'approve': [('readonly', False)]}, + help="Show remaining advance of this employee", + ) + residual = fields.Monetary( + string='Amount to clear', + compute='_compute_residual', + store=True, + help="Amount to clear of this expense sheet in company currency", + ) + advance_sheet_residual = fields.Monetary( + string='Advance Remaining', + related='advance_sheet_id.residual', + store=True, + help="Remaining amount to clear the selected advance sheet", + ) + amount_payable = fields.Monetary( + string='Payable Amount', + compute='_compute_amount_payable', + help="Final regiter payment amount even after advance clearing", + ) + + @api.multi + @api.depends('expense_line_ids') + def _compute_advance(self): + for sheet in self: + sheet.advance = bool(sheet.expense_line_ids.filtered('advance') and + len(sheet.expense_line_ids) == 1) + return + + @api.one + @api.constrains('advance_sheet_id', 'expense_line_ids') + def _check_advance_expense(self): + advance_lines = self.expense_line_ids.filtered('advance') + if self.advance_sheet_id and advance_lines: + raise ValidationError(_('Advance clearing must not contain any ' + 'advance expense line')) + if advance_lines and len(self.expense_line_ids) != 1: + raise ValidationError(_('Advance must contain only 1 ' + 'advance expense line')) + + @api.one + @api.depends('account_move_id.line_ids.amount_residual') + def _compute_residual(self): + residual_company = 0.0 + emp_advance = self.env.ref('hr_expense_advance_clearing.' + 'product_emp_advance') + for line in self.sudo().account_move_id.line_ids: + if line.account_id == emp_advance.property_account_expense_id: + residual_company += line.amount_residual + self.residual = residual_company + + @api.multi + def _compute_amount_payable(self): + for sheet in self: + rec_lines = sheet.account_move_id.line_ids.filtered( + lambda x: + x.credit and x.account_id.reconcile and not x.reconciled) + sheet.amount_payable = -sum(rec_lines.mapped('amount_residual')) + + @api.multi + def action_sheet_move_create(self): + res = super(HrExpenseSheet, self).action_sheet_move_create() + # Reconcile advance of this sheet with the advance_sheet + emp_advance = self.env.ref('hr_expense_advance_clearing.' + 'product_emp_advance') + for sheet in self: + move_lines = sheet.account_move_id.line_ids | \ + sheet.advance_sheet_id.account_move_id.line_ids + account_id = emp_advance.property_account_expense_id.id + adv_move_lines = self.env['account.move.line'].sudo().search([ + ('id', 'in', move_lines.ids), ('account_id', '=', account_id)]) + adv_move_lines.reconcile() + return res + + @api.multi + def open_clear_advance(self): + self.ensure_one() + action = self.env.ref('hr_expense_advance_clearing.' + 'action_hr_expense_sheet_advance_clearing') + vals = action.read()[0] + context1 = vals.get('context', {}) + if isinstance(context1, pycompat.string_types): + context1 = safe_eval(context1) + context1['default_advance_sheet_id'] = self.id + vals['context'] = context1 + return vals diff --git a/hr_expense_advance_clearing/readme/CONFIGURE.rst b/hr_expense_advance_clearing/readme/CONFIGURE.rst new file mode 100644 index 000000000..4af425617 --- /dev/null +++ b/hr_expense_advance_clearing/readme/CONFIGURE.rst @@ -0,0 +1,10 @@ +This module will create a new product "Employee Advance" automatically. +You will need to setup the Expense Account of this product to your Employee Advance account manually. + +* Open Product window and search for "Employee Advance" +* On Accounting tab, select appropriate employee advance account from your chart of account + +Note: + +* You will need the "Show Full Accounting Features" to see accounting data +* Employee Advance account code, if not already exists, you can create one. Use type = Current Asset and check Allow Reconciliation. diff --git a/hr_expense_advance_clearing/readme/CONTRIBUTORS.rst b/hr_expense_advance_clearing/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..033e67f43 --- /dev/null +++ b/hr_expense_advance_clearing/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Kitti Upariphutthiphong diff --git a/hr_expense_advance_clearing/readme/DESCRIPTION.rst b/hr_expense_advance_clearing/readme/DESCRIPTION.rst new file mode 100644 index 000000000..8dd91962f --- /dev/null +++ b/hr_expense_advance_clearing/readme/DESCRIPTION.rst @@ -0,0 +1,11 @@ +Standard Expenses module allow employee to do the expense reimbursement only after the expense has been made. +In other world, employee will need to pay first and reimburse later. + +This module, allow company to advance an amount to the employee. +Employee can then use that advance amount to purchase product/service first, then back to company and do the clearing. + +There can be 3 scenarios for advance and clearing + +* When clearing amount = advance amount, no other operation is required. +* When clearing amount > advance amount, company will pay the extra to employee. +* When clearing amount < advance amount, employee will return the remain to company. diff --git a/hr_expense_advance_clearing/readme/INSTALL.rst b/hr_expense_advance_clearing/readme/INSTALL.rst new file mode 100644 index 000000000..f381baecb --- /dev/null +++ b/hr_expense_advance_clearing/readme/INSTALL.rst @@ -0,0 +1 @@ +* No specific installation required diff --git a/hr_expense_advance_clearing/readme/USAGE.rst b/hr_expense_advance_clearing/readme/USAGE.rst new file mode 100644 index 000000000..6f9499b71 --- /dev/null +++ b/hr_expense_advance_clearing/readme/USAGE.rst @@ -0,0 +1,32 @@ +**Create an Employee Advance** + +#. Go to Expenses > My Expenses +#. Create a new Expense as normal, but also check "Employee Advance" checkbox +#. Product = Employee Advance will be set automatically, do not change. +#. Set the unit price to advance amount +#. Click Create Report as normal. Please note that, this expense report will also has flag "Employee Advance" checked. +#. As normal, do Submit to Manager > Approve > Post Journal Entries > Register Payment. +#. As this is Advance, you will see new field "Amount to clear". + +**Clear Advance** + +#. Go to Expenses > My Expense Report. +#. Search for the Advance you want to clear, or use filter "Advance (not cleared)" to see all uncleared advance. +#. Open an Advance which is now in paid status with some Amount to be cleared. +#. Click button "Clear Advance", system will create new Expense Report with reference to the previous step Advance. +#. Add or create Expense line(s) as normal. +#. As normal, do Submit to Manager > Approve > Post Journal Entries + +Note: + +* If the total expense amount less than or equal to the advance amount, the status will be set to Paid right after post journal entries. +* If the total expense amount more than the advance amount, Register Payment will pay the extra amount then set state to Paid. + +**Return Advance** + +#. Go to Expenses > My Expense Report. +#. Search for the Advance you want to clear, or use filter "Advance (not cleared)" to see all uncleared advance. +#. Open an Advance which is now in paid status with some Amount to be cleared. +#. Click button "Return Advance" will open Register Payment wizard with Amount to clear. +#. Click button "Validate" to return that amount back +#. All returned, Amount to clear is now equal to 0.0 diff --git a/hr_expense_advance_clearing/static/description/icon.png b/hr_expense_advance_clearing/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 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 z z?n?eq1XP>p-IM$Z^C;2L3itnbJZAip*Zo0aw2bs8@(s^~*8T9go!%dHcAz2lM;`yp zD=7&xjFV$S&5uDaiScyD?B-i1ze`+CoRtz`Wn+Zl&#s4&}MO{@N!ufrzjG$B79)Y2d3tBk&)TxUTw@QS0TEL_?njX|@vq?Uz(nBFK5Pq7*xj#u*R&i|?7+6# z+|r_n#SW&LXhtheZdah{ZVoqwyT{D>MC3nkFF#N)xLi{p7J1jXlmVeb;cP5?e(=f# zuT7fvjSbjS781v?7{)-X3*?>tq?)Yd)~|1{BDS(pqC zC}~H#WXlkUW*H5CDOo<)#x7%RY)A;ShGhI5s*#cRDA8YgqG(HeKDx+#(ZQ?386dv! zlXCO)w91~Vw4AmOcATuV653fa9R$fyK8ul%rG z-wfS zihugoZyr38Im?Zuh6@RcF~t1anQu7>#lPpb#}4cOA!EM11`%f*07RqOVkmX{p~KJ9 z^zP;K#|)$`^Rb{rnHGH{~>1(fawV0*Z#)}M`m8-?ZJV<+e}s9wE# z)l&az?w^5{)`S(%MRzxdNqrs1n*-=jS^_jqE*5XDrA0+VE`5^*p3CuM<&dZEeCjoz zR;uu_H9ZPZV|fQq`Cyw4nscrVwi!fE6ciMmX$!_hN7uF;jjKG)d2@aC4ropY)8etW=xJvni)8eHi`H$%#zn^WJ5NLc-rqk|u&&4Z6fD_m&JfSI1Bvb?b<*n&sfl0^t z=HnmRl`XrFvMKB%9}>PaA`m-fK6a0(8=qPkWS5bb4=v?XcWi&hRY?O5HdulRi4?fN zlsJ*N-0Qw+Yic@s0(2uy%F@ib;GjXt01Fmx5XbRo6+n|pP(&nodMoap^z{~q ziEeaUT@Mxe3vJSfI6?uLND(CNr=#^W<1b}jzW58bIfyWTDle$mmS(|x-0|2UlX+9k zQ^EX7Nw}?EzVoBfT(-LT|=9N@^hcn-_p&sqG z&*oVs2JSU+N4ZD`FhCAWaS;>|wH2G*Id|?pa#@>tyxX`+4HyIArWDvVrX)2WAOQff z0qyHu&-S@i^MS-+j--!pr4fPBj~_8({~e1bfcl0wI1kaoN>mJL6KUPQm5N7lB(ui1 zE-o%kq)&djzWJ}ob<-GfDlkB;F31j-VHKvQUGQ3sp`CwyGJk_i!y^sD0fqC@$9|jO zOqN!r!8-p==F@ZVP=U$qSpY(gQ0)59P1&t@y?5rvg<}E+GB}26NYPp4f2YFQrQtot5mn3wu_qprZ=>Ig-$ zbW26Ws~IgY>}^5w`vTB(G`PTZaDiGBo5o(tp)qli|NeV( z@H_=R8V39rt5J5YB2Ky?4eJJ#b`_iBe2ot~6%7mLt5t8Vwi^Jy7|jWXqa3amOIoRb zOr}WVFP--DsS`1WpN%~)t3R!arKF^Q$e12KEqU36AWwnCBICpH4XCsfnyrHr>$I$4 z!DpKX$OKLWarN7nv@!uIA+~RNO)l$$w}p(;b>mx8pwYvu;dD_unryX_NhT8*Tj>BTrTTL&!?O+%Rv;b?B??gSzdp?6Uug9{ zd@V08Z$BdI?fpoCS$)t4mg4rT8Q_I}h`0d-vYZ^|dOB*Q^S|xqTV*vIg?@fVFSmMpaw0qtTRbx} z({Pg?#{2`sc9)M5N$*N|4;^t$+QP?#mov zGVC@I*lBVrOU-%2y!7%)fAKjpEFsgQc4{amtiHb95KQEwvf<(3T<9-Zm$xIew#P22 zc2Ix|App^>v6(3L_MCU0d3W##AB0M~3D00EWoKZqsJYT(#@w$Y_H7G22M~ApVFTRHMI_3be)Lkn#0F*V8Pq zc}`Cjy$bE;FJ6H7p=0y#R>`}-m4(0F>%@P|?7fx{=R^uFdISRnZ2W_xQhD{YuR3t< z{6yxu=4~JkeA;|(J6_nv#>Nvs&FuLA&PW^he@t(UwFFE8)|a!R{`E`K`i^ZnyE4$k z;(749Ix|oi$c3QbEJ3b~D_kQsPz~fIUKym($a_7dJ?o+40*OLl^{=&oq$<#Q(yyrp z{J-FAniyAw9tPbe&IhQ|a`DqFTVQGQ&Gq3!C2==4x{6EJwiPZ8zub-iXoUtkJiG{} zPaR&}_fn8_z~(=;5lD-aPWD3z8PZS@AaUiomF!G8I}Mf>e~0g#BelA-5#`cj;O5>N Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 diff --git a/hr_expense_advance_clearing/static/description/index.html b/hr_expense_advance_clearing/static/description/index.html new file mode 100644 index 000000000..9da747210 --- /dev/null +++ b/hr_expense_advance_clearing/static/description/index.html @@ -0,0 +1,487 @@ + + + + + + +Employee Advance and Clearing + + + +
+

Employee Advance and Clearing

+ + +

Beta License: AGPL-3 OCA/hr Translate me on Weblate Try me on Runbot

+

Standard Expenses module allow employee to do the expense reimbursement only after the expense has been made. +In other world, employee will need to pay first and reimburse later.

+

This module, allow company to advance an amount to the employee. +Employee can then use that advance amount to purchase product/service first, then back to company and do the clearing.

+

There can be 3 scenarios for advance and clearing

+
    +
  • When clearing amount = advance amount, no other operation is required.
  • +
  • When clearing amount > advance amount, company will pay the extra to employee.
  • +
  • When clearing amount < advance amount, employee will return the remain to company.
  • +
+

Table of contents

+ +
+

Installation

+
    +
  • No specific installation required
  • +
+
+
+

Configuration

+

This module will create a new product “Employee Advance” automatically. +You will need to setup the Expense Account of this product to your Employee Advance account manually.

+
    +
  • Open Product window and search for “Employee Advance”
  • +
  • On Accounting tab, select appropriate employee advance account from your chart of account
  • +
+

Note:

+
    +
  • You will need the “Show Full Accounting Features” to see accounting data
  • +
  • Employee Advance account code, if not already exists, you can create one. Use type = Current Asset and check Allow Reconciliation.
  • +
+
+
+

Usage

+

Create an Employee Advance

+
    +
  1. Go to Expenses > My Expenses
  2. +
  3. Create a new Expense as normal, but also check “Employee Advance” checkbox
  4. +
  5. Product = Employee Advance will be set automatically, do not change.
  6. +
  7. Set the unit price to advance amount
  8. +
  9. Click Create Report as normal. Please note that, this expense report will also has flag “Employee Advance” checked.
  10. +
  11. As normal, do Submit to Manager > Approve > Post Journal Entries > Register Payment.
  12. +
  13. As this is Advance, you will see new field “Amount to clear”.
  14. +
+

Clear Advance

+
    +
  1. Go to Expenses > My Expense Report.
  2. +
  3. Search for the Advance you want to clear, or use filter “Advance (not cleared)” to see all uncleared advance.
  4. +
  5. Open an Advance which is now in paid status with some Amount to be cleared.
  6. +
  7. Click button “Clear Advance”, system will create new Expense Report with reference to the previous step Advance.
  8. +
  9. Add or create Expense line(s) as normal.
  10. +
  11. As normal, do Submit to Manager > Approve > Post Journal Entries
  12. +
+

Note:

+
    +
  • If the total expense amount less than or equal to the advance amount, the status will be set to Paid right after post journal entries.
  • +
  • If the total expense amount more than the advance amount, Register Payment will pay the extra amount then set state to Paid.
  • +
+

Return Advance

+
    +
  1. Go to Expenses > My Expense Report.
  2. +
  3. Search for the Advance you want to clear, or use filter “Advance (not cleared)” to see all uncleared advance.
  4. +
  5. Open an Advance which is now in paid status with some Amount to be cleared.
  6. +
  7. Click button “Return Advance” will open Register Payment wizard with Amount to clear.
  8. +
  9. Click button “Validate” to return that amount back
  10. +
  11. All returned, Amount to clear is now equal to 0.0
  12. +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Ecosoft
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/hr project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/hr_expense_advance_clearing/tests/__init__.py b/hr_expense_advance_clearing/tests/__init__.py new file mode 100644 index 000000000..6867ba88a --- /dev/null +++ b/hr_expense_advance_clearing/tests/__init__.py @@ -0,0 +1 @@ +from . import test_hr_expense_advance_clearing diff --git a/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py b/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py new file mode 100644 index 000000000..5b99ff158 --- /dev/null +++ b/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py @@ -0,0 +1,205 @@ +# Copyright 2019 Kitti Upariphutthiphong +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.tests import common +from odoo.tests.common import Form +from odoo.exceptions import ValidationError + + +class TestHrExpenseAdvanceClearing(common.SavepointCase): + + @classmethod + def setUpClass(cls): + super(TestHrExpenseAdvanceClearing, cls).setUpClass() + company = cls.env.ref('base.main_company') + cls.journal_bank = cls.env['account.journal'].search( + [('type', '=', 'bank')], limit=1) + cls.product = cls.env['product.product'].create({ + 'name': 'Service 1', + 'type': 'service', + }) + tax_account = cls.env['account.account'].search([ + ('company_id', '=', company.id), + ('user_type_id', '=', cls.env.ref( + 'account.data_account_type_non_current_liabilities').id) + ], limit=1) + tax_group = cls.env['account.tax.group'].create({ + 'name': 'Tax Group 1', 'sequence': 1, }) + cls.tax = cls.env['account.tax'].create({ + 'name': 'Tax 10.0%', + 'amount': 10.0, + 'amount_type': 'percent', + 'type_tax_use': 'purchase', + 'account_id': tax_account.id, + 'company_id': company.id, + 'refund_account_id': tax_account.id, + 'tax_group_id': tax_group.id, + }) + employee_home = cls.env['res.partner'].create({ + 'name': 'Employee Home Address', + }) + cls.employee = cls.env['hr.employee'].create({ + 'name': 'Employee A', + 'address_home_id': employee_home.id, + }) + advance_account = cls.env['account.account'].create({ + 'code': '154000', + 'name': 'Employee Advance', + 'user_type_id': cls.env.ref( + 'account.data_account_type_current_assets').id, + 'reconcile': True, + }) + cls.emp_advance = cls.env.ref('hr_expense_advance_clearing.' + 'product_emp_advance') + cls.emp_advance.property_account_expense_id = advance_account + # Create advance expense 1,000 + cls.advance = cls._create_expense_sheet( + cls, 'Advance 1,000', cls.employee, cls.emp_advance, 1000.0, + advance=True) + # Create clearing expense 1,000 + cls.clearing_equal = cls._create_expense_sheet( + cls, 'Buy service 1,000', cls.employee, cls.product, 1000.0) + # Create clearing expense 1,200 + cls.clearing_more = cls._create_expense_sheet( + cls, 'Buy service 1,200', cls.employee, cls.product, 1200.0) + # Create clearing expense 800 + cls.clearing_less = cls._create_expense_sheet( + cls, 'Buy service 800', cls.employee, cls.product, 800.0) + + def _create_expense(self, description, employee, + product, amount, advance=False, + payment_mode='own_account'): + with Form(self.env['hr.expense']) as expense: + expense.advance = advance + expense.name = description + expense.employee_id = employee + expense.product_id = product + expense.unit_amount = amount + expense.payment_mode = payment_mode + expense = expense.save() + expense.tax_ids = False # Test no vat + return expense + + def _create_expense_sheet(self, description, employee, + product, amount, advance=False): + expense = self._create_expense(self, description, employee, + product, amount, advance) + # Add expense to expense sheet + expense_sheet = self.env['hr.expense.sheet'].create({ + 'name': description, + 'employee_id': expense.employee_id.id, + 'expense_line_ids': [(6, 0, [expense.id])], + }) + return expense_sheet + + def _register_payment(self, expense_sheet, hr_return_advance=False): + ctx = {'active_ids': [expense_sheet.id], + 'active_id': expense_sheet.id, + 'hr_return_advance': hr_return_advance, } + PaymentWizard = self.env['hr.expense.sheet.register.payment.wizard'] + with Form(PaymentWizard.with_context(ctx)) as f: + f.journal_id = self.journal_bank + payment_wizard = f.save() + payment_wizard.expense_post_payment() + + def test_0_test_constraints(self): + """ Test some constraints """ + # Advance Sheet can't be clearing at the same time. + with self.assertRaises(ValidationError): + self.advance.advance_sheet_id = self.advance + # Advance Sheet should not have > 1 expense lines + with self.assertRaises(ValidationError): + expense = self._create_expense( + 'Buy service 1,000', self.employee, self.product, 1.0) + self.advance.write({'expense_line_ids': [(4, expense.id)]}) + # Advance Expense's product must be employee advance + with self.assertRaises(ValidationError): + expense = self._create_expense( + 'Advance 1,000', self.employee, self.product, 1.0, + advance=True) + # Advance Expense's product, must not has tax involved + with self.assertRaises(ValidationError): + self.emp_advance.supplier_taxes_id |= self.tax + expense = self._create_expense( + 'Advance 1,000', self.employee, self.emp_advance, 1.0, + advance=True) + self.emp_advance.supplier_taxes_id = False # Remove tax bf proceed + # Advance Expense, must not paid by company + with self.assertRaises(ValidationError): + expense = self._create_expense( + 'Advance 1,000', self.employee, self.emp_advance, 1.0, + advance=True, payment_mode='company_account') + # Advance Expense's product must have account configured + with self.assertRaises(ValidationError): + self.emp_advance.property_account_expense_id = False + expense = self._create_expense( + 'Advance 1,000', self.employee, self.emp_advance, 1.0, + advance=True) + + def test_1_clear_equal_advance(self): + """ When clear equal advance, all set """ + # ------------------ Advance -------------------------- + self.advance.action_submit_sheet() + self.advance.approve_expense_sheets() + self.advance.action_sheet_move_create() + self.assertEqual(self.advance.residual, 1000.0) + self._register_payment(self.advance) + self.assertEqual(self.advance.state, 'done') + # ------------------ Clearing -------------------------- + # Clear this with previous advance + vals = self.advance.open_clear_advance() # Test Clear Advance button + ctx = vals.get('context') + self.assertEqual(ctx['default_advance_sheet_id'], self.advance.id) + self.clearing_equal.advance_sheet_id = self.advance + self.assertEqual(self.clearing_equal.advance_sheet_residual, 1000.0) + self.clearing_equal.action_submit_sheet() + self.clearing_equal.approve_expense_sheets() + self.clearing_equal.action_sheet_move_create() + # Equal amount, state change to Paid and advance is cleared + self.assertEqual(self.clearing_equal.state, 'done') + self.assertEqual(self.clearing_equal.advance_sheet_residual, 0.0) + + def test_2_clear_more_than_advance(self): + """ When clear more than advance, do pay more """ + # ------------------ Advance -------------------------- + self.advance.action_submit_sheet() + self.advance.approve_expense_sheets() + self.advance.action_sheet_move_create() + self.assertEqual(self.advance.residual, 1000.0) + self._register_payment(self.advance) + self.assertEqual(self.advance.state, 'done') + # ------------------ Clearing -------------------------- + # Clear this with previous advance + self.clearing_more.advance_sheet_id = self.advance + self.assertEqual(self.clearing_more.advance_sheet_residual, 1000.0) + self.clearing_more.action_submit_sheet() + self.clearing_more.approve_expense_sheets() + self.clearing_more.action_sheet_move_create() + # More amount, state not changed to paid, and has to pay 200 more + self.assertEqual(self.clearing_more.state, 'post') + self.assertEqual(self.clearing_more.amount_payable, 200.0) + self._register_payment(self.clearing_more) + self.assertEqual(self.clearing_more.state, 'done') + + def test_3_clear_less_than_advance(self): + """ When clear less than advance, do return advance """ + # ------------------ Advance -------------------------- + self.advance.action_submit_sheet() + self.advance.approve_expense_sheets() + self.advance.action_sheet_move_create() + self.assertEqual(self.advance.residual, 1000.0) + self._register_payment(self.advance) + self.assertEqual(self.advance.state, 'done') + # ------------------ Clearing -------------------------- + # Clear this with previous advance + self.clearing_less.advance_sheet_id = self.advance + self.assertEqual(self.clearing_less.advance_sheet_residual, 1000.0) + self.clearing_less.action_submit_sheet() + self.clearing_less.approve_expense_sheets() + self.clearing_less.action_sheet_move_create() + # Less amount, state set to done. Still remain 200 to be returned + self.assertEqual(self.clearing_less.state, 'done') + self.assertEqual(self.clearing_less.advance_sheet_residual, 200.0) + # Back to advance and do return advance, residual become 0.0 + self._register_payment(self.advance, hr_return_advance=True) + self.assertEqual(self.advance.residual, 0.0) diff --git a/hr_expense_advance_clearing/views/hr_expense_views.xml b/hr_expense_advance_clearing/views/hr_expense_views.xml new file mode 100644 index 000000000..0d5802d88 --- /dev/null +++ b/hr_expense_advance_clearing/views/hr_expense_views.xml @@ -0,0 +1,70 @@ + + + + hr.expense.sheet.filter + hr.expense.sheet + + + + + + + + + + + + Advance Clearing + hr.expense.sheet + form + + [('employee_id.user_id', '=', uid), ('state', '!=', 'cancel')] + {'search_default_my_reports': 1} + +

+ Create a new expense report +

+ Once you have created your expense, submit it to your manager who will validate it. +

+
+
+ + + hr.expense.form + hr.expense + + +

+

+
+
+ + + view.hr.expense.sheet.form + hr.expense.sheet + + + +
From db2b074781123580a103cb22b7b11a8db94d8ace Mon Sep 17 00:00:00 2001 From: kittiu Date: Fri, 20 Mar 2020 18:33:41 +0700 Subject: [PATCH 34/75] [IMP] : black, isort (cherry picked from commit df4f68f889f28ccf34d6d407b3f997dd613e1dcb) --- hr_expense_advance_clearing/__manifest__.py | 26 +-- .../data/advance_product.xml | 8 +- .../models/account_payment.py | 9 +- .../models/hr_expense.py | 59 +++--- .../models/hr_expense_sheet.py | 100 +++++---- .../tests/test_hr_expense_advance_clearing.py | 198 ++++++++++-------- .../views/hr_expense_views.xml | 87 +++++--- .../hr_expense_sheet_register_payment.py | 51 ++--- 8 files changed, 304 insertions(+), 234 deletions(-) diff --git a/hr_expense_advance_clearing/__manifest__.py b/hr_expense_advance_clearing/__manifest__.py index 4b0563dbe..17aae78ca 100644 --- a/hr_expense_advance_clearing/__manifest__.py +++ b/hr_expense_advance_clearing/__manifest__.py @@ -2,20 +2,14 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). { - 'name': 'Employee Advance and Clearing', - 'version': '12.0.1.0.1', - 'category': 'Human Resources', - 'author': 'Ecosoft, ' - 'Odoo Community Association (OCA)', - 'license': 'AGPL-3', - 'website': 'https://github.com/OCA/hr', - 'depends': [ - 'hr_expense', - ], - 'data': [ - 'data/advance_product.xml', - 'views/hr_expense_views.xml', - ], - 'installable': True, - 'maintainers': ['kittiu'], + "name": "Employee Advance and Clearing", + "version": "12.0.1.0.1", + "category": "Human Resources", + "author": "Ecosoft, " "Odoo Community Association (OCA)", + "license": "AGPL-3", + "website": "https://github.com/OCA/hr", + "depends": ["hr_expense",], + "data": ["data/advance_product.xml", "views/hr_expense_views.xml",], + "installable": True, + "maintainers": ["kittiu"], } diff --git a/hr_expense_advance_clearing/data/advance_product.xml b/hr_expense_advance_clearing/data/advance_product.xml index 3784ef122..cca45ffa2 100644 --- a/hr_expense_advance_clearing/data/advance_product.xml +++ b/hr_expense_advance_clearing/data/advance_product.xml @@ -1,11 +1,11 @@ Employee Advance - + service - - - + + + False False diff --git a/hr_expense_advance_clearing/models/account_payment.py b/hr_expense_advance_clearing/models/account_payment.py index 165563790..b7275bb29 100644 --- a/hr_expense_advance_clearing/models/account_payment.py +++ b/hr_expense_advance_clearing/models/account_payment.py @@ -5,12 +5,11 @@ class AccountPayment(models.Model): - _inherit = 'account.payment' + _inherit = "account.payment" def _get_counterpart_move_line_vals(self, invoice=False): - emp_advance = self.env.ref('hr_expense_advance_clearing.' - 'product_emp_advance') + emp_advance = self.env.ref("hr_expense_advance_clearing." "product_emp_advance") res = super()._get_counterpart_move_line_vals(invoice=invoice) - if self._context.get('hr_return_advance', False): - res['account_id'] = emp_advance.property_account_expense_id.id + if self._context.get("hr_return_advance", False): + res["account_id"] = emp_advance.property_account_expense_id.id return res diff --git a/hr_expense_advance_clearing/models/hr_expense.py b/hr_expense_advance_clearing/models/hr_expense.py index 58a9ab8ff..0995eadcd 100644 --- a/hr_expense_advance_clearing/models/hr_expense.py +++ b/hr_expense_advance_clearing/models/hr_expense.py @@ -1,45 +1,44 @@ # Copyright 2019 Kitti Upariphutthiphong # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import api, fields, models, _ +from odoo import _, api, fields, models from odoo.exceptions import ValidationError class HrExpense(models.Model): - _inherit = 'hr.expense' + _inherit = "hr.expense" - advance = fields.Boolean( - string='Employee Advance', - default=False, - ) + advance = fields.Boolean(string="Employee Advance", default=False,) @api.multi - @api.constrains('advance') + @api.constrains("advance") def _check_advance(self): - for expense in self.filtered('advance'): - emp_advance = self.env.ref('hr_expense_advance_clearing.' - 'product_emp_advance') + for expense in self.filtered("advance"): + emp_advance = self.env.ref( + "hr_expense_advance_clearing." "product_emp_advance" + ) if not emp_advance.property_account_expense_id: raise ValidationError( - _('Employee advance product has no payable account')) + _("Employee advance product has no payable account") + ) if expense.product_id != emp_advance: raise ValidationError( - _('Employee advance, selected product is not valid')) + _("Employee advance, selected product is not valid") + ) if expense.tax_ids: - raise ValidationError( - _('Employee advance, all taxes must be removed')) - if expense.payment_mode != 'own_account': - raise ValidationError( - _('Employee advance, paid by must be employee')) + raise ValidationError(_("Employee advance, all taxes must be removed")) + if expense.payment_mode != "own_account": + raise ValidationError(_("Employee advance, paid by must be employee")) return True - @api.onchange('advance') + @api.onchange("advance") def onchange_advance(self): self.tax_ids = False - self.payment_mode = 'own_account' + self.payment_mode = "own_account" if self.advance: self.product_id = self.env.ref( - 'hr_expense_advance_clearing.product_emp_advance') + "hr_expense_advance_clearing.product_emp_advance" + ) else: self.product_id = False @@ -47,35 +46,35 @@ def onchange_advance(self): def _get_account_move_line_values(self): move_line_values_by_expense = super()._get_account_move_line_values() # Only when do the clearing, change cr payable to cr advance - emp_advance = self.env.ref('hr_expense_advance_clearing.' - 'product_emp_advance') - sheets = self.mapped('sheet_id').filtered('advance_sheet_id') + emp_advance = self.env.ref("hr_expense_advance_clearing." "product_emp_advance") + sheets = self.mapped("sheet_id").filtered("advance_sheet_id") sheets_x = sheets.filtered(lambda x: x.advance_sheet_residual <= 0.0) if sheets_x: # Advance Sheets with no residual left - raise ValidationError(_('Advance: %s has no amount to clear') % - ', '.join(sheets_x.mapped('name'))) + raise ValidationError( + _("Advance: %s has no amount to clear") + % ", ".join(sheets_x.mapped("name")) + ) for sheet in sheets: advance_to_clear = sheet.advance_sheet_residual for expense_id, move_lines in move_line_values_by_expense.items(): payable_move_line = False for move_line in move_lines: - credit = move_line['credit'] + credit = move_line["credit"] if not credit: continue # cr payable -> cr advance remain_payable = 0.0 if credit > advance_to_clear: remain_payable = credit - advance_to_clear - move_line['credit'] = advance_to_clear + move_line["credit"] = advance_to_clear advance_to_clear = 0.0 # extra payable line payable_move_line = move_line.copy() - payable_move_line['credit'] = remain_payable + payable_move_line["credit"] = remain_payable else: advance_to_clear -= credit # advance line - move_line['account_id'] = \ - emp_advance.property_account_expense_id.id + move_line["account_id"] = emp_advance.property_account_expense_id.id if payable_move_line: move_lines.append(payable_move_line) return move_line_values_by_expense diff --git a/hr_expense_advance_clearing/models/hr_expense_sheet.py b/hr_expense_advance_clearing/models/hr_expense_sheet.py index 96465b012..f77b216cb 100644 --- a/hr_expense_advance_clearing/models/hr_expense_sheet.py +++ b/hr_expense_advance_clearing/models/hr_expense_sheet.py @@ -1,74 +1,79 @@ # Copyright 2019 Kitti Upariphutthiphong # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import api, models, fields, _ -from odoo.tools.safe_eval import safe_eval -from odoo.tools import pycompat +from odoo import _, api, fields, models from odoo.exceptions import ValidationError +from odoo.tools import pycompat +from odoo.tools.safe_eval import safe_eval class HrExpenseSheet(models.Model): - _inherit = 'hr.expense.sheet' + _inherit = "hr.expense.sheet" advance = fields.Boolean( - string='Employee Advance', - compute='_compute_advance', - store=True, + string="Employee Advance", compute="_compute_advance", store=True, ) advance_sheet_id = fields.Many2one( - comodel_name='hr.expense.sheet', - string='Clear Advance', + comodel_name="hr.expense.sheet", + string="Clear Advance", domain="[('advance', '=', True), ('employee_id', '=', employee_id)," - " ('residual', '>', 0.0)]", + " ('residual', '>', 0.0)]", readonly=True, - states={'draft': [('readonly', False)], - 'submit': [('readonly', False)], - 'approve': [('readonly', False)]}, + states={ + "draft": [("readonly", False)], + "submit": [("readonly", False)], + "approve": [("readonly", False)], + }, help="Show remaining advance of this employee", ) residual = fields.Monetary( - string='Amount to clear', - compute='_compute_residual', + string="Amount to clear", + compute="_compute_residual", store=True, help="Amount to clear of this expense sheet in company currency", ) advance_sheet_residual = fields.Monetary( - string='Advance Remaining', - related='advance_sheet_id.residual', + string="Advance Remaining", + related="advance_sheet_id.residual", store=True, help="Remaining amount to clear the selected advance sheet", ) amount_payable = fields.Monetary( - string='Payable Amount', - compute='_compute_amount_payable', + string="Payable Amount", + compute="_compute_amount_payable", help="Final regiter payment amount even after advance clearing", ) @api.multi - @api.depends('expense_line_ids') + @api.depends("expense_line_ids") def _compute_advance(self): for sheet in self: - sheet.advance = bool(sheet.expense_line_ids.filtered('advance') and - len(sheet.expense_line_ids) == 1) + sheet.advance = bool( + sheet.expense_line_ids.filtered("advance") + and len(sheet.expense_line_ids) == 1 + ) return @api.one - @api.constrains('advance_sheet_id', 'expense_line_ids') + @api.constrains("advance_sheet_id", "expense_line_ids") def _check_advance_expense(self): - advance_lines = self.expense_line_ids.filtered('advance') + advance_lines = self.expense_line_ids.filtered("advance") if self.advance_sheet_id and advance_lines: - raise ValidationError(_('Advance clearing must not contain any ' - 'advance expense line')) + raise ValidationError( + _("Advance clearing must not contain any " "advance expense line") + ) if advance_lines and len(self.expense_line_ids) != 1: - raise ValidationError(_('Advance must contain only 1 ' - 'advance expense line')) + raise ValidationError( + _("Advance must contain only 1 " "advance expense line") + ) @api.one - @api.depends('account_move_id.line_ids.amount_residual') + @api.depends("account_move_id.line_ids.amount_residual") def _compute_residual(self): residual_company = 0.0 - emp_advance = self.env.ref('hr_expense_advance_clearing.' - 'product_emp_advance', False) + emp_advance = self.env.ref( + "hr_expense_advance_clearing." "product_emp_advance", False + ) if emp_advance: for line in self.sudo().account_move_id.line_ids: if line.account_id == emp_advance.property_account_expense_id: @@ -79,34 +84,39 @@ def _compute_residual(self): def _compute_amount_payable(self): for sheet in self: rec_lines = sheet.account_move_id.line_ids.filtered( - lambda x: - x.credit and x.account_id.reconcile and not x.reconciled) - sheet.amount_payable = -sum(rec_lines.mapped('amount_residual')) + lambda x: x.credit and x.account_id.reconcile and not x.reconciled + ) + sheet.amount_payable = -sum(rec_lines.mapped("amount_residual")) @api.multi def action_sheet_move_create(self): res = super(HrExpenseSheet, self).action_sheet_move_create() # Reconcile advance of this sheet with the advance_sheet - emp_advance = self.env.ref('hr_expense_advance_clearing.' - 'product_emp_advance') + emp_advance = self.env.ref("hr_expense_advance_clearing." "product_emp_advance") for sheet in self: - move_lines = sheet.account_move_id.line_ids | \ - sheet.advance_sheet_id.account_move_id.line_ids + move_lines = ( + sheet.account_move_id.line_ids + | sheet.advance_sheet_id.account_move_id.line_ids + ) account_id = emp_advance.property_account_expense_id.id - adv_move_lines = self.env['account.move.line'].sudo().search([ - ('id', 'in', move_lines.ids), ('account_id', '=', account_id)]) + adv_move_lines = ( + self.env["account.move.line"] + .sudo() + .search([("id", "in", move_lines.ids), ("account_id", "=", account_id)]) + ) adv_move_lines.reconcile() return res @api.multi def open_clear_advance(self): self.ensure_one() - action = self.env.ref('hr_expense_advance_clearing.' - 'action_hr_expense_sheet_advance_clearing') + action = self.env.ref( + "hr_expense_advance_clearing." "action_hr_expense_sheet_advance_clearing" + ) vals = action.read()[0] - context1 = vals.get('context', {}) + context1 = vals.get("context", {}) if isinstance(context1, pycompat.string_types): context1 = safe_eval(context1) - context1['default_advance_sheet_id'] = self.id - vals['context'] = context1 + context1["default_advance_sheet_id"] = self.id + vals["context"] = context1 return vals diff --git a/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py b/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py index 5b99ff158..a15a61358 100644 --- a/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py +++ b/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py @@ -1,75 +1,95 @@ # Copyright 2019 Kitti Upariphutthiphong # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo.exceptions import ValidationError from odoo.tests import common from odoo.tests.common import Form -from odoo.exceptions import ValidationError class TestHrExpenseAdvanceClearing(common.SavepointCase): - @classmethod def setUpClass(cls): super(TestHrExpenseAdvanceClearing, cls).setUpClass() - company = cls.env.ref('base.main_company') - cls.journal_bank = cls.env['account.journal'].search( - [('type', '=', 'bank')], limit=1) - cls.product = cls.env['product.product'].create({ - 'name': 'Service 1', - 'type': 'service', - }) - tax_account = cls.env['account.account'].search([ - ('company_id', '=', company.id), - ('user_type_id', '=', cls.env.ref( - 'account.data_account_type_non_current_liabilities').id) - ], limit=1) - tax_group = cls.env['account.tax.group'].create({ - 'name': 'Tax Group 1', 'sequence': 1, }) - cls.tax = cls.env['account.tax'].create({ - 'name': 'Tax 10.0%', - 'amount': 10.0, - 'amount_type': 'percent', - 'type_tax_use': 'purchase', - 'account_id': tax_account.id, - 'company_id': company.id, - 'refund_account_id': tax_account.id, - 'tax_group_id': tax_group.id, - }) - employee_home = cls.env['res.partner'].create({ - 'name': 'Employee Home Address', - }) - cls.employee = cls.env['hr.employee'].create({ - 'name': 'Employee A', - 'address_home_id': employee_home.id, - }) - advance_account = cls.env['account.account'].create({ - 'code': '154000', - 'name': 'Employee Advance', - 'user_type_id': cls.env.ref( - 'account.data_account_type_current_assets').id, - 'reconcile': True, - }) - cls.emp_advance = cls.env.ref('hr_expense_advance_clearing.' - 'product_emp_advance') + company = cls.env.ref("base.main_company") + cls.journal_bank = cls.env["account.journal"].search( + [("type", "=", "bank")], limit=1 + ) + cls.product = cls.env["product.product"].create( + {"name": "Service 1", "type": "service",} + ) + tax_account = cls.env["account.account"].search( + [ + ("company_id", "=", company.id), + ( + "user_type_id", + "=", + cls.env.ref("account.data_account_type_non_current_liabilities").id, + ), + ], + limit=1, + ) + tax_group = cls.env["account.tax.group"].create( + {"name": "Tax Group 1", "sequence": 1,} + ) + cls.tax = cls.env["account.tax"].create( + { + "name": "Tax 10.0%", + "amount": 10.0, + "amount_type": "percent", + "type_tax_use": "purchase", + "account_id": tax_account.id, + "company_id": company.id, + "refund_account_id": tax_account.id, + "tax_group_id": tax_group.id, + } + ) + employee_home = cls.env["res.partner"].create( + {"name": "Employee Home Address",} + ) + cls.employee = cls.env["hr.employee"].create( + {"name": "Employee A", "address_home_id": employee_home.id,} + ) + advance_account = cls.env["account.account"].create( + { + "code": "154000", + "name": "Employee Advance", + "user_type_id": cls.env.ref( + "account.data_account_type_current_assets" + ).id, + "reconcile": True, + } + ) + cls.emp_advance = cls.env.ref( + "hr_expense_advance_clearing." "product_emp_advance" + ) cls.emp_advance.property_account_expense_id = advance_account # Create advance expense 1,000 cls.advance = cls._create_expense_sheet( - cls, 'Advance 1,000', cls.employee, cls.emp_advance, 1000.0, - advance=True) + cls, "Advance 1,000", cls.employee, cls.emp_advance, 1000.0, advance=True + ) # Create clearing expense 1,000 cls.clearing_equal = cls._create_expense_sheet( - cls, 'Buy service 1,000', cls.employee, cls.product, 1000.0) + cls, "Buy service 1,000", cls.employee, cls.product, 1000.0 + ) # Create clearing expense 1,200 cls.clearing_more = cls._create_expense_sheet( - cls, 'Buy service 1,200', cls.employee, cls.product, 1200.0) + cls, "Buy service 1,200", cls.employee, cls.product, 1200.0 + ) # Create clearing expense 800 cls.clearing_less = cls._create_expense_sheet( - cls, 'Buy service 800', cls.employee, cls.product, 800.0) + cls, "Buy service 800", cls.employee, cls.product, 800.0 + ) - def _create_expense(self, description, employee, - product, amount, advance=False, - payment_mode='own_account'): - with Form(self.env['hr.expense']) as expense: + def _create_expense( + self, + description, + employee, + product, + amount, + advance=False, + payment_mode="own_account", + ): + with Form(self.env["hr.expense"]) as expense: expense.advance = advance expense.name = description expense.employee_id = employee @@ -80,23 +100,29 @@ def _create_expense(self, description, employee, expense.tax_ids = False # Test no vat return expense - def _create_expense_sheet(self, description, employee, - product, amount, advance=False): - expense = self._create_expense(self, description, employee, - product, amount, advance) + def _create_expense_sheet( + self, description, employee, product, amount, advance=False + ): + expense = self._create_expense( + self, description, employee, product, amount, advance + ) # Add expense to expense sheet - expense_sheet = self.env['hr.expense.sheet'].create({ - 'name': description, - 'employee_id': expense.employee_id.id, - 'expense_line_ids': [(6, 0, [expense.id])], - }) + expense_sheet = self.env["hr.expense.sheet"].create( + { + "name": description, + "employee_id": expense.employee_id.id, + "expense_line_ids": [(6, 0, [expense.id])], + } + ) return expense_sheet def _register_payment(self, expense_sheet, hr_return_advance=False): - ctx = {'active_ids': [expense_sheet.id], - 'active_id': expense_sheet.id, - 'hr_return_advance': hr_return_advance, } - PaymentWizard = self.env['hr.expense.sheet.register.payment.wizard'] + ctx = { + "active_ids": [expense_sheet.id], + "active_id": expense_sheet.id, + "hr_return_advance": hr_return_advance, + } + PaymentWizard = self.env["hr.expense.sheet.register.payment.wizard"] with Form(PaymentWizard.with_context(ctx)) as f: f.journal_id = self.journal_bank payment_wizard = f.save() @@ -110,31 +136,37 @@ def test_0_test_constraints(self): # Advance Sheet should not have > 1 expense lines with self.assertRaises(ValidationError): expense = self._create_expense( - 'Buy service 1,000', self.employee, self.product, 1.0) - self.advance.write({'expense_line_ids': [(4, expense.id)]}) + "Buy service 1,000", self.employee, self.product, 1.0 + ) + self.advance.write({"expense_line_ids": [(4, expense.id)]}) # Advance Expense's product must be employee advance with self.assertRaises(ValidationError): expense = self._create_expense( - 'Advance 1,000', self.employee, self.product, 1.0, - advance=True) + "Advance 1,000", self.employee, self.product, 1.0, advance=True + ) # Advance Expense's product, must not has tax involved with self.assertRaises(ValidationError): self.emp_advance.supplier_taxes_id |= self.tax expense = self._create_expense( - 'Advance 1,000', self.employee, self.emp_advance, 1.0, - advance=True) + "Advance 1,000", self.employee, self.emp_advance, 1.0, advance=True + ) self.emp_advance.supplier_taxes_id = False # Remove tax bf proceed # Advance Expense, must not paid by company with self.assertRaises(ValidationError): expense = self._create_expense( - 'Advance 1,000', self.employee, self.emp_advance, 1.0, - advance=True, payment_mode='company_account') + "Advance 1,000", + self.employee, + self.emp_advance, + 1.0, + advance=True, + payment_mode="company_account", + ) # Advance Expense's product must have account configured with self.assertRaises(ValidationError): self.emp_advance.property_account_expense_id = False expense = self._create_expense( - 'Advance 1,000', self.employee, self.emp_advance, 1.0, - advance=True) + "Advance 1,000", self.employee, self.emp_advance, 1.0, advance=True + ) def test_1_clear_equal_advance(self): """ When clear equal advance, all set """ @@ -144,19 +176,19 @@ def test_1_clear_equal_advance(self): self.advance.action_sheet_move_create() self.assertEqual(self.advance.residual, 1000.0) self._register_payment(self.advance) - self.assertEqual(self.advance.state, 'done') + self.assertEqual(self.advance.state, "done") # ------------------ Clearing -------------------------- # Clear this with previous advance vals = self.advance.open_clear_advance() # Test Clear Advance button - ctx = vals.get('context') - self.assertEqual(ctx['default_advance_sheet_id'], self.advance.id) + ctx = vals.get("context") + self.assertEqual(ctx["default_advance_sheet_id"], self.advance.id) self.clearing_equal.advance_sheet_id = self.advance self.assertEqual(self.clearing_equal.advance_sheet_residual, 1000.0) self.clearing_equal.action_submit_sheet() self.clearing_equal.approve_expense_sheets() self.clearing_equal.action_sheet_move_create() # Equal amount, state change to Paid and advance is cleared - self.assertEqual(self.clearing_equal.state, 'done') + self.assertEqual(self.clearing_equal.state, "done") self.assertEqual(self.clearing_equal.advance_sheet_residual, 0.0) def test_2_clear_more_than_advance(self): @@ -167,7 +199,7 @@ def test_2_clear_more_than_advance(self): self.advance.action_sheet_move_create() self.assertEqual(self.advance.residual, 1000.0) self._register_payment(self.advance) - self.assertEqual(self.advance.state, 'done') + self.assertEqual(self.advance.state, "done") # ------------------ Clearing -------------------------- # Clear this with previous advance self.clearing_more.advance_sheet_id = self.advance @@ -176,10 +208,10 @@ def test_2_clear_more_than_advance(self): self.clearing_more.approve_expense_sheets() self.clearing_more.action_sheet_move_create() # More amount, state not changed to paid, and has to pay 200 more - self.assertEqual(self.clearing_more.state, 'post') + self.assertEqual(self.clearing_more.state, "post") self.assertEqual(self.clearing_more.amount_payable, 200.0) self._register_payment(self.clearing_more) - self.assertEqual(self.clearing_more.state, 'done') + self.assertEqual(self.clearing_more.state, "done") def test_3_clear_less_than_advance(self): """ When clear less than advance, do return advance """ @@ -189,7 +221,7 @@ def test_3_clear_less_than_advance(self): self.advance.action_sheet_move_create() self.assertEqual(self.advance.residual, 1000.0) self._register_payment(self.advance) - self.assertEqual(self.advance.state, 'done') + self.assertEqual(self.advance.state, "done") # ------------------ Clearing -------------------------- # Clear this with previous advance self.clearing_less.advance_sheet_id = self.advance @@ -198,7 +230,7 @@ def test_3_clear_less_than_advance(self): self.clearing_less.approve_expense_sheets() self.clearing_less.action_sheet_move_create() # Less amount, state set to done. Still remain 200 to be returned - self.assertEqual(self.clearing_less.state, 'done') + self.assertEqual(self.clearing_less.state, "done") self.assertEqual(self.clearing_less.advance_sheet_residual, 200.0) # Back to advance and do return advance, residual become 0.0 self._register_payment(self.advance, hr_return_advance=True) diff --git a/hr_expense_advance_clearing/views/hr_expense_views.xml b/hr_expense_advance_clearing/views/hr_expense_views.xml index 0d5802d88..5852779d6 100644 --- a/hr_expense_advance_clearing/views/hr_expense_views.xml +++ b/hr_expense_advance_clearing/views/hr_expense_views.xml @@ -1,70 +1,103 @@ - hr.expense.sheet.filter hr.expense.sheet - + - - - + + + - Advance Clearing hr.expense.sheet form - - [('employee_id.user_id', '=', uid), ('state', '!=', 'cancel')] + + [('employee_id.user_id', '=', uid), ('state', '!=', 'cancel')] {'search_default_my_reports': 1} -

+

Create a new expense report -

+

+

Once you have created your expense, submit it to your manager who will validate it.

- hr.expense.form hr.expense - +

-

- view.hr.expense.sheet.form hr.expense.sheet - + - + + + +
diff --git a/hr_expense_advance_clearing/views/hr_employee_views.xml b/hr_expense_advance_clearing/views/hr_employee_views.xml new file mode 100644 index 000000000..b558ac1f2 --- /dev/null +++ b/hr_expense_advance_clearing/views/hr_employee_views.xml @@ -0,0 +1,20 @@ + + + + hr.employee.form + hr.employee + + + + + + + + diff --git a/hr_expense_advance_clearing/views/hr_expense_views.xml b/hr_expense_advance_clearing/views/hr_expense_views.xml index 4aae2f8e9..2cb011acd 100644 --- a/hr_expense_advance_clearing/views/hr_expense_views.xml +++ b/hr_expense_advance_clearing/views/hr_expense_views.xml @@ -75,6 +75,16 @@ + + hr.expense.sheet.tree + hr.expense.sheet + + + + + + + view.hr.expense.sheet.form hr.expense.sheet From 5bf2cc61a0a19bca1f9285f65f247ed87a04d097 Mon Sep 17 00:00:00 2001 From: Kitti U Date: Tue, 2 Nov 2021 11:33:52 +0700 Subject: [PATCH 45/75] [14.0][IMP] hr_expense_advance_clearing, add menus Advance/Expense (cherry picked from commit e6c8719d3b391aabff4ac8ccad6508ddf46e73ca) --- .../models/hr_expense.py | 3 - .../models/hr_expense_sheet.py | 6 +- .../tests/test_hr_expense_advance_clearing.py | 5 +- .../views/hr_expense_views.xml | 84 ++++++++++++++++++- 4 files changed, 88 insertions(+), 10 deletions(-) diff --git a/hr_expense_advance_clearing/models/hr_expense.py b/hr_expense_advance_clearing/models/hr_expense.py index 848a60e7d..a9c599416 100644 --- a/hr_expense_advance_clearing/models/hr_expense.py +++ b/hr_expense_advance_clearing/models/hr_expense.py @@ -54,9 +54,6 @@ def onchange_advance(self): self.product_id = self.env.ref( "hr_expense_advance_clearing.product_emp_advance" ) - else: - self.product_id = False - self.clearing_product_id = False def _get_account_move_line_values(self): move_line_values_by_expense = super()._get_account_move_line_values() diff --git a/hr_expense_advance_clearing/models/hr_expense_sheet.py b/hr_expense_advance_clearing/models/hr_expense_sheet.py index ebda34a78..ea80aba13 100644 --- a/hr_expense_advance_clearing/models/hr_expense_sheet.py +++ b/hr_expense_advance_clearing/models/hr_expense_sheet.py @@ -53,8 +53,10 @@ class HrExpenseSheet(models.Model): @api.depends("expense_line_ids") def _compute_advance(self): for sheet in self: - sheet.advance = sheet.expense_line_ids and all( - sheet.expense_line_ids.mapped("advance") + sheet.advance = ( + sheet.expense_line_ids + and all(sheet.expense_line_ids.mapped("advance")) + or self.env.context.get("default_advance") ) return diff --git a/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py b/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py index aa11b086b..d276f9dc6 100644 --- a/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py +++ b/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py @@ -74,8 +74,9 @@ def _create_expense( advance=False, payment_mode="own_account", ): - with Form(self.env["hr.expense"]) as expense: - expense.advance = advance + with Form( + self.env["hr.expense"].with_context(default_advance=advance) + ) as expense: expense.name = description expense.employee_id = employee if not advance: diff --git a/hr_expense_advance_clearing/views/hr_expense_views.xml b/hr_expense_advance_clearing/views/hr_expense_views.xml index 2cb011acd..2235bca63 100644 --- a/hr_expense_advance_clearing/views/hr_expense_views.xml +++ b/hr_expense_advance_clearing/views/hr_expense_views.xml @@ -1,4 +1,77 @@ + + + + + Advances + hr.expense.sheet + tree,kanban,form,pivot,graph,activity + + [('state', '!=', 'cancel'), ('advance', '=', True)] + {'search_default_my_reports': 1, 'default_advance': True} + +

+ No advance report found. Let's create one! +

+ Once you have created your advance, submit it to your manager who will validate it. +

+
+
+ + Expenses + hr.expense.sheet + tree,kanban,form,pivot,graph,activity + + [('state', '!=', 'cancel'), ('advance', '=', False)] + {'search_default_my_reports': 1} + +

+ No expense report found. Let's create one! +

+ Once you have created your expense, submit it to your manager who will validate it. +

+
+
+ + + + + + + hr.expense.view.search + hr.expense + + + + + + + + hr.expense.sheet.view.search hr.expense.sheet @@ -47,8 +120,13 @@

- -

{'default_advance': advance, 'form_view_ref' : 'hr_expense.hr_expense_view_form_without_header', 'default_company_id': company_id, 'default_employee_id': employee_id} + >{'default_advance': advance, 'search_default_is_advance': advance, 'search_default_is_expense': not advance, 'form_view_ref' : 'hr_expense.hr_expense_view_form_without_header', 'default_company_id': company_id, 'default_employee_id': employee_id} Date: Tue, 9 Nov 2021 14:48:22 +0700 Subject: [PATCH 46/75] [FIX] constrains with advance clearing (cherry picked from commit 963dff4a82b22ab9dc303082b43254c71b8875ed) --- .../models/hr_expense.py | 4 ++++ .../tests/test_hr_expense_advance_clearing.py | 23 +++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/hr_expense_advance_clearing/models/hr_expense.py b/hr_expense_advance_clearing/models/hr_expense.py index a9c599416..d13139113 100644 --- a/hr_expense_advance_clearing/models/hr_expense.py +++ b/hr_expense_advance_clearing/models/hr_expense.py @@ -40,6 +40,10 @@ def _check_advance(self): raise ValidationError( _("Employee advance, selected product is not valid") ) + if expense.account_id != emp_advance.property_account_expense_id: + raise ValidationError( + _("Employee advance, account must be the same payable account") + ) if expense.tax_ids: raise ValidationError(_("Employee advance, all taxes must be removed")) if expense.payment_mode != "own_account": diff --git a/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py b/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py index d276f9dc6..9d16ca150 100644 --- a/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py +++ b/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py @@ -44,9 +44,14 @@ def setUpClass(cls): "reconcile": True, } ) - cls.emp_advance = cls.env.ref( - "hr_expense_advance_clearing." "product_emp_advance" + cls.account_sales = cls.env["account.account"].create( + { + "code": "X1020", + "name": "Product Sales - (test)", + "user_type_id": cls.env.ref("account.data_account_type_revenue").id, + } ) + cls.emp_advance = cls.env.ref("hr_expense_advance_clearing.product_emp_advance") cls.emp_advance.property_account_expense_id = advance_account # Create advance expense 1,000 cls.advance = cls._create_expense_sheet( @@ -73,6 +78,7 @@ def _create_expense( amount, advance=False, payment_mode="own_account", + account=False, ): with Form( self.env["hr.expense"].with_context(default_advance=advance) @@ -83,6 +89,8 @@ def _create_expense( expense.product_id = product expense.unit_amount = amount expense.payment_mode = payment_mode + if account: + expense.account_id = account expense = expense.save() expense.tax_ids = False # Test no vat return expense @@ -121,6 +129,17 @@ def test_0_test_constraints(self): # Advance Sheet can't be clearing at the same time. with self.assertRaises(ValidationError): self.advance.advance_sheet_id = self.advance + # Advance Sheet can't change account is not the equal + # Account on Advance Expense's product. + with self.assertRaises(ValidationError): + expense = self._create_expense( + "Advance 1,000", + self.employee, + self.emp_advance, + 1.0, + advance=True, + account=self.account_sales, + ) # Advance Sheet should not have > 1 expense lines with self.assertRaises(ValidationError): expense = self._create_expense( From 2ddd3816f5a8a91e30b9b9542069ed2d9c2bc447 Mon Sep 17 00:00:00 2001 From: ps-tubtim Date: Thu, 11 Nov 2021 16:59:36 +0700 Subject: [PATCH 47/75] [FIX] hr_expense_advance_clearing: add Clear Advance on tree view (cherry picked from commit e68024181272c370f6410e6b7ae44187b3e9b5f7) --- hr_expense_advance_clearing/views/hr_expense_views.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hr_expense_advance_clearing/views/hr_expense_views.xml b/hr_expense_advance_clearing/views/hr_expense_views.xml index 2235bca63..218eb9e78 100644 --- a/hr_expense_advance_clearing/views/hr_expense_views.xml +++ b/hr_expense_advance_clearing/views/hr_expense_views.xml @@ -158,6 +158,9 @@ hr.expense.sheet + + + From b6671b4d6270b592c756227de2eb5b204d42d2f4 Mon Sep 17 00:00:00 2001 From: Saran440 Date: Thu, 18 Nov 2021 11:10:17 +0700 Subject: [PATCH 48/75] [FIX] skip_account_move_synchronization on post journal entries (cherry picked from commit e6d6691bb90ddbda303ad9bd74a52f924851e02c) --- hr_expense_advance_clearing/models/hr_expense_sheet.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/hr_expense_advance_clearing/models/hr_expense_sheet.py b/hr_expense_advance_clearing/models/hr_expense_sheet.py index ea80aba13..378b564ca 100644 --- a/hr_expense_advance_clearing/models/hr_expense_sheet.py +++ b/hr_expense_advance_clearing/models/hr_expense_sheet.py @@ -94,6 +94,8 @@ def action_sheet_move_create(self): res = super(HrExpenseSheet, self).action_sheet_move_create() # Reconcile advance of this sheet with the advance_sheet emp_advance = self.env.ref("hr_expense_advance_clearing.product_emp_advance") + ctx = self._context.copy() + ctx.update({"skip_account_move_synchronization": True}) for sheet in self: move_lines = ( sheet.account_move_id.line_ids @@ -111,7 +113,10 @@ def action_sheet_move_create(self): ] ) ) - adv_move_lines.reconcile() + adv_move_lines.with_context(ctx).reconcile() + # Update state on clearing advance when advance residual > total amount + if sheet.advance_sheet_id and advance_residual != -1: + sheet.write({"state": "done"}) return res def open_clear_advance(self): From 80315ce724e8470e9fea0e82743fa8a913b9d325 Mon Sep 17 00:00:00 2001 From: Kitti U Date: Wed, 10 Nov 2021 22:20:25 +0700 Subject: [PATCH 49/75] [14.0][FIX] hr_expense_advance_clearing, make expense_line_ids readonly on new clearing (cherry picked from commit fbbf3038974f37be2a8367bfbece0eef5349d28e) --- hr_expense_advance_clearing/models/hr_expense_sheet.py | 9 ++++----- hr_expense_advance_clearing/views/hr_expense_views.xml | 8 +++++++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/hr_expense_advance_clearing/models/hr_expense_sheet.py b/hr_expense_advance_clearing/models/hr_expense_sheet.py index 378b564ca..bc02810f6 100644 --- a/hr_expense_advance_clearing/models/hr_expense_sheet.py +++ b/hr_expense_advance_clearing/models/hr_expense_sheet.py @@ -53,11 +53,10 @@ class HrExpenseSheet(models.Model): @api.depends("expense_line_ids") def _compute_advance(self): for sheet in self: - sheet.advance = ( - sheet.expense_line_ids - and all(sheet.expense_line_ids.mapped("advance")) - or self.env.context.get("default_advance") - ) + if sheet.expense_line_ids: + sheet.advance = all(sheet.expense_line_ids.mapped("advance")) + else: + sheet.advance = self.env.context.get("default_advance", sheet.advance) return @api.constrains("advance_sheet_id", "expense_line_ids") diff --git a/hr_expense_advance_clearing/views/hr_expense_views.xml b/hr_expense_advance_clearing/views/hr_expense_views.xml index 218eb9e78..3d8b13786 100644 --- a/hr_expense_advance_clearing/views/hr_expense_views.xml +++ b/hr_expense_advance_clearing/views/hr_expense_views.xml @@ -198,9 +198,11 @@ + {'default_advance': advance, 'search_default_is_advance': advance, 'search_default_is_expense': not advance, 'form_view_ref' : 'hr_expense.hr_expense_view_form_without_header', 'default_company_id': company_id, 'default_employee_id': employee_id} + {'readonly': [('id', '=', False), ('advance_sheet_id', '!=', False)]} + True Date: Mon, 22 Nov 2021 17:20:01 +0700 Subject: [PATCH 50/75] [FIX] onchange clearing advance for hooks (cherry picked from commit 9728fd37b5165e83ba5ae59203ba49ed65117062) --- .../models/hr_expense_sheet.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/hr_expense_advance_clearing/models/hr_expense_sheet.py b/hr_expense_advance_clearing/models/hr_expense_sheet.py index bc02810f6..dd89cdb4e 100644 --- a/hr_expense_advance_clearing/models/hr_expense_sheet.py +++ b/hr_expense_advance_clearing/models/hr_expense_sheet.py @@ -131,15 +131,21 @@ def open_clear_advance(self): vals["context"] = context1 return vals + def get_domain_advance_sheet_expense_line(self): + return self.advance_sheet_id.expense_line_ids.filtered("clearing_product_id") + + def create_clearing_expense_line(self, line): + clear_advance = self._prepare_clear_advance(line) + clearing_line = self.env["hr.expense"].new(clear_advance) + return clearing_line + @api.onchange("advance_sheet_id") def _onchange_advance_sheet_id(self): self.expense_line_ids -= self.expense_line_ids.filtered("av_line_id") self.advance_sheet_id.expense_line_ids.sudo().read() # prefetch - for line in self.advance_sheet_id.expense_line_ids.filtered( - "clearing_product_id" - ): - clear_advance = self._prepare_clear_advance(line) - self.expense_line_ids += self.env["hr.expense"].new(clear_advance) + lines = self.get_domain_advance_sheet_expense_line() + for line in lines: + self.expense_line_ids += self.create_clearing_expense_line(line) def _prepare_clear_advance(self, line): # Prepare the clearing expense From 4d3e926cf01f23962ac6d96e6d5a4ee965b1c79b Mon Sep 17 00:00:00 2001 From: ps-tubtim Date: Fri, 26 Nov 2021 10:12:22 +0700 Subject: [PATCH 51/75] [FIX] hr_expense_advance_clearing: add context for clearing (cherry picked from commit c9ad5bc01c15c3a96633e67093977d5368c70421) --- hr_expense_advance_clearing/models/hr_expense_sheet.py | 1 + 1 file changed, 1 insertion(+) diff --git a/hr_expense_advance_clearing/models/hr_expense_sheet.py b/hr_expense_advance_clearing/models/hr_expense_sheet.py index dd89cdb4e..28479c785 100644 --- a/hr_expense_advance_clearing/models/hr_expense_sheet.py +++ b/hr_expense_advance_clearing/models/hr_expense_sheet.py @@ -128,6 +128,7 @@ def open_clear_advance(self): if context1: context1 = safe_eval(context1) context1["default_advance_sheet_id"] = self.id + context1["default_employee_id"] = self.employee_id.id vals["context"] = context1 return vals From f5edf57524ac0361af478de831418acc9a81490f Mon Sep 17 00:00:00 2001 From: Kitti U Date: Thu, 3 Mar 2022 12:39:17 +0700 Subject: [PATCH 52/75] [15.0][MIG] hr_expense_advance_clearing] (cherry picked from commit ee44183a291c0b376a94a4a542a8aee20b7e745a) --- .../tests/test_hr_expense_advance_clearing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py b/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py index 9d16ca150..a17f842b9 100644 --- a/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py +++ b/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py @@ -6,7 +6,7 @@ from odoo.tests.common import Form -class TestHrExpenseAdvanceClearing(common.SavepointCase): +class TestHrExpenseAdvanceClearing(common.TransactionCase): @classmethod def setUpClass(cls): super(TestHrExpenseAdvanceClearing, cls).setUpClass() From 07cfa99b2da143a4cae4bcc1bc0d92661b7768aa Mon Sep 17 00:00:00 2001 From: Saran440 Date: Sat, 26 Mar 2022 15:59:31 +0700 Subject: [PATCH 53/75] [FIX] do not allow cancel PV related clearing or return (cherry picked from commit 6f13bc852935fa13b4ee86138d53997d4bd2fa58) --- .../models/__init__.py | 1 + .../models/account_move.py | 38 +++++++++++++++++++ .../models/hr_expense.py | 1 - 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 hr_expense_advance_clearing/models/account_move.py diff --git a/hr_expense_advance_clearing/models/__init__.py b/hr_expense_advance_clearing/models/__init__.py index be323457c..154043cdd 100644 --- a/hr_expense_advance_clearing/models/__init__.py +++ b/hr_expense_advance_clearing/models/__init__.py @@ -1,3 +1,4 @@ from . import hr_expense from . import hr_expense_sheet from . import hr_employee_base +from . import account_move diff --git a/hr_expense_advance_clearing/models/account_move.py b/hr_expense_advance_clearing/models/account_move.py new file mode 100644 index 000000000..636aea513 --- /dev/null +++ b/hr_expense_advance_clearing/models/account_move.py @@ -0,0 +1,38 @@ +# Copyright 2022 Ecosoft Co., Ltd. (https://ecosoft.co.th) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import _, models +from odoo.exceptions import UserError + + +class AccountMove(models.Model): + _inherit = "account.move" + + def _check_hr_advance_move_reconciled(self): + """Check if the advance move lines already cleard/returned""" + av_moves = self.filtered("line_ids.expense_id.sheet_id.advance") + emp_advance = self.env.ref("hr_expense_advance_clearing.product_emp_advance") + reconciled_av_move_lines = av_moves.mapped("line_ids").filtered( + lambda l: l.product_id == emp_advance and l.matching_number + ) + if reconciled_av_move_lines: + raise UserError( + _( + "This operation is not allowed as some advance amount was already " + "cleared/returned.\nPlease cancel those documents first." + ) + ) + + def button_draft(self): + self._check_hr_advance_move_reconciled() + return super().button_draft() + + def button_cancel(self): + self._check_hr_advance_move_reconciled() + return super().button_cancel() + + def _reverse_moves(self, default_values_list=None, cancel=False): + self._check_hr_advance_move_reconciled() + return super()._reverse_moves( + default_values_list=default_values_list, cancel=cancel + ) diff --git a/hr_expense_advance_clearing/models/hr_expense.py b/hr_expense_advance_clearing/models/hr_expense.py index d13139113..1e0e3476c 100644 --- a/hr_expense_advance_clearing/models/hr_expense.py +++ b/hr_expense_advance_clearing/models/hr_expense.py @@ -53,7 +53,6 @@ def _check_advance(self): @api.onchange("advance") def onchange_advance(self): self.tax_ids = False - self.payment_mode = "own_account" if self.advance: self.product_id = self.env.ref( "hr_expense_advance_clearing.product_emp_advance" From 8272c9b8a28cf604ae860ada7ad953afdd3aef9f Mon Sep 17 00:00:00 2001 From: ps-tubtim Date: Mon, 14 Nov 2022 17:31:50 +0700 Subject: [PATCH 54/75] [14.0][FIX] hr_expense_advance_clearing: allow general user to create clearing (cherry picked from commit 8d5a3969ead1d1bb9b560ab9328b7f88340ace6f) --- hr_expense_advance_clearing/views/hr_expense_views.xml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/hr_expense_advance_clearing/views/hr_expense_views.xml b/hr_expense_advance_clearing/views/hr_expense_views.xml index 3d8b13786..0a447ef69 100644 --- a/hr_expense_advance_clearing/views/hr_expense_views.xml +++ b/hr_expense_advance_clearing/views/hr_expense_views.xml @@ -178,15 +178,14 @@ string="Clear Advance" class="oe_highlight" attrs="{'invisible': ['|', '|', ('advance', '=', False), ('state', '!=', 'done'), ('clearing_residual', '=', 0.0)]}" - groups="account.group_account_manager" /> diff --git a/hr_expense_advance_clearing/views/hr_expense_views.xml b/hr_expense_advance_clearing/views/hr_expense_views.xml index 1fe1dc6ff..80be553a7 100644 --- a/hr_expense_advance_clearing/views/hr_expense_views.xml +++ b/hr_expense_advance_clearing/views/hr_expense_views.xml @@ -1,60 +1,7 @@ + - - - - - Advances - hr.expense.sheet - tree,kanban,form,pivot,graph,activity - - [('state', '!=', 'cancel'), ('advance', '=', True)] - {'search_default_my_reports': 1, 'default_advance': True} - -

- No advance report found. Let's create one! -

- Once you have created your advance, submit it to your manager who will validate it. -

-
-
- - Expenses - hr.expense.sheet - tree,kanban,form,pivot,graph,activity - - [('state', '!=', 'cancel'), ('advance', '=', False)] - {'search_default_my_reports': 1} - -

- No expense report found. Let's create one! -

- Once you have created your expense, submit it to your manager who will validate it. -

-
-
- - - - - - hr.expense.view.search + hr.expense.view.search - advance_clearing hr.expense @@ -62,200 +9,89 @@ - - - - - - hr.expense.sheet.view.search - hr.expense.sheet - - - - - - Advance Clearing - hr.expense.sheet - form - - - [('employee_id.user_id', '=', uid), ('state', '!=', 'cancel')] - - {'search_default_my_reports': 1} - -

- Create a new expense report -

-

- Once you have created your expense, submit it to your manager who will validate it. -

-
-
- - hr.expense.form + + + hr.expense.view.form - advance_clearing hr.expense

- -

{'readonly': [('advance', '=', True)]} + name="readonly" + >state != 'draft' or expense_type == 'advance' 1 - - - {'invisible': [('advance', '!=', False)]} - - - {'invisible': [('advance', '!=', False)]} - - -
- - hr.expense.sheet.tree - hr.expense.sheet - - - - - - - - - - - - view.hr.expense.sheet.form - hr.expense.sheet - - - - + +
+ + +
+
+
From 97bb6af121fd51f189f9650d3eaf684e45362ff1 Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Fri, 19 Jun 2026 09:25:55 -0400 Subject: [PATCH 74/75] [ADD] hr_expense_advance_clearing_trip: tie an advance to a trip (prototype) Bridge module (auto_install) depending on hr_expense_advance_clearing + hr_expense_trip. Adds a Trip Advance on the trip and an 'Apply Advance' action that clears the trip's regular expenses against it in one step. --- hr_expense_advance_clearing_trip/README.rst | 105 ++++ hr_expense_advance_clearing_trip/__init__.py | 1 + .../__manifest__.py | 18 + .../models/__init__.py | 1 + .../models/hr_trip.py | 53 +++ .../pyproject.toml | 3 + .../readme/CONTRIBUTORS.md | 3 + .../readme/DESCRIPTION.md | 3 + .../readme/USAGE.md | 4 + .../static/description/index.html | 450 ++++++++++++++++++ .../tests/__init__.py | 1 + .../test_hr_expense_advance_clearing_trip.py | 75 +++ .../views/hr_trip_views.xml | 28 ++ 13 files changed, 745 insertions(+) create mode 100644 hr_expense_advance_clearing_trip/README.rst create mode 100644 hr_expense_advance_clearing_trip/__init__.py create mode 100644 hr_expense_advance_clearing_trip/__manifest__.py create mode 100644 hr_expense_advance_clearing_trip/models/__init__.py create mode 100644 hr_expense_advance_clearing_trip/models/hr_trip.py create mode 100644 hr_expense_advance_clearing_trip/pyproject.toml create mode 100644 hr_expense_advance_clearing_trip/readme/CONTRIBUTORS.md create mode 100644 hr_expense_advance_clearing_trip/readme/DESCRIPTION.md create mode 100644 hr_expense_advance_clearing_trip/readme/USAGE.md create mode 100644 hr_expense_advance_clearing_trip/static/description/index.html create mode 100644 hr_expense_advance_clearing_trip/tests/__init__.py create mode 100644 hr_expense_advance_clearing_trip/tests/test_hr_expense_advance_clearing_trip.py create mode 100644 hr_expense_advance_clearing_trip/views/hr_trip_views.xml diff --git a/hr_expense_advance_clearing_trip/README.rst b/hr_expense_advance_clearing_trip/README.rst new file mode 100644 index 000000000..0761323d6 --- /dev/null +++ b/hr_expense_advance_clearing_trip/README.rst @@ -0,0 +1,105 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + +================================ +HR Expense Advance Clearing Trip +================================ + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:a8ca889f8be002a0d3fb73711695845e611500f8267cf9d930d04f453d7c5821 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png + :target: https://odoo-community.org/page/development-status + :alt: Alpha +.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fhr--expense-lightgray.png?logo=github + :target: https://github.com/OCA/hr-expense/tree/19.0/hr_expense_advance_clearing_trip + :alt: OCA/hr-expense +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/hr-expense-19-0/hr-expense-19-0-hr_expense_advance_clearing_trip + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/hr-expense&target_branch=19.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +Links an employee **advance** to a **trip** so the trip's expenses can +be cleared against that advance in one step, rather than setting the +advance on each expense line individually. + +.. IMPORTANT:: + This is an alpha version, the data model and design can change at any time without warning. + Only for development or testing purpose, do not use in production. + `More details on development status `_ + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +On a trip, set the **Trip Advance** (an advance-type expense for the +same employee) and click **Apply Advance**. Every regular expense on the +trip that is not yet linked to an advance is cleared against the trip +advance. The **Advance Remaining** field shows the residual to clear. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Ledo + +Contributors +------------ + +- Ledo + + - Don Kendall + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +.. |maintainer-dnplkndll| image:: https://github.com/dnplkndll.png?size=40px + :target: https://github.com/dnplkndll + :alt: dnplkndll + +Current `maintainer `__: + +|maintainer-dnplkndll| + +This module is part of the `OCA/hr-expense `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/hr_expense_advance_clearing_trip/__init__.py b/hr_expense_advance_clearing_trip/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/hr_expense_advance_clearing_trip/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/hr_expense_advance_clearing_trip/__manifest__.py b/hr_expense_advance_clearing_trip/__manifest__.py new file mode 100644 index 000000000..2868b52df --- /dev/null +++ b/hr_expense_advance_clearing_trip/__manifest__.py @@ -0,0 +1,18 @@ +# Copyright 2026 Ledo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +{ + "name": "HR Expense Advance Clearing Trip", + "version": "19.0.1.0.0", + "category": "Human Resources", + "summary": "Tie an employee advance to a trip and clear the trip's expenses", + "author": "Ledo, Odoo Community Association (OCA)", + "maintainers": ["dnplkndll"], + "license": "AGPL-3", + "development_status": "Alpha", + "website": "https://github.com/OCA/hr-expense", + "depends": ["hr_expense_advance_clearing", "hr_expense_trip"], + "data": ["views/hr_trip_views.xml"], + "installable": True, + "auto_install": True, +} diff --git a/hr_expense_advance_clearing_trip/models/__init__.py b/hr_expense_advance_clearing_trip/models/__init__.py new file mode 100644 index 000000000..4692f2534 --- /dev/null +++ b/hr_expense_advance_clearing_trip/models/__init__.py @@ -0,0 +1 @@ +from . import hr_trip diff --git a/hr_expense_advance_clearing_trip/models/hr_trip.py b/hr_expense_advance_clearing_trip/models/hr_trip.py new file mode 100644 index 000000000..55b90e699 --- /dev/null +++ b/hr_expense_advance_clearing_trip/models/hr_trip.py @@ -0,0 +1,53 @@ +# Copyright 2026 Ledo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import api, fields, models +from odoo.exceptions import UserError + + +class HrTrip(models.Model): + _inherit = "hr.trip" + + advance_id = fields.Many2one( + comodel_name="hr.expense", + string="Trip Advance", + domain="[('expense_type', '=', 'advance'), ('employee_id', '=', employee_id)]", + help="Advance paid to the employee for this trip. Use 'Apply Advance' " + "to clear the trip's expenses against it.", + ) + advance_residual = fields.Monetary( + related="advance_id.clearing_residual", + string="Advance Remaining", + ) + currency_id = fields.Many2one(related="advance_id.currency_id") + + @api.onchange("employee_id") + def _onchange_employee_clear_advance(self): + # The advance must belong to the trip's employee. + if self.advance_id and self.advance_id.employee_id != self.employee_id: + self.advance_id = False + + def action_apply_advance(self): + """Clear the trip's regular expenses against the trip advance, by + setting clearing_advance_id on each one that is not already cleared.""" + self.ensure_one() + if not self.advance_id: + raise UserError(self.env._("Set a Trip Advance first.")) + clearings = self.expense_ids.filtered( + lambda expense: expense.expense_type == "expense" + and not expense.clearing_advance_id + ) + if not clearings: + raise UserError( + self.env._("No unlinked regular expenses to clear on this trip.") + ) + clearings.write({"clearing_advance_id": self.advance_id.id}) + + def action_view_advance(self): + self.ensure_one() + return { + "type": "ir.actions.act_window", + "res_model": "hr.expense", + "res_id": self.advance_id.id, + "view_mode": "form", + } diff --git a/hr_expense_advance_clearing_trip/pyproject.toml b/hr_expense_advance_clearing_trip/pyproject.toml new file mode 100644 index 000000000..4231d0ccc --- /dev/null +++ b/hr_expense_advance_clearing_trip/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/hr_expense_advance_clearing_trip/readme/CONTRIBUTORS.md b/hr_expense_advance_clearing_trip/readme/CONTRIBUTORS.md new file mode 100644 index 000000000..df56e4bb3 --- /dev/null +++ b/hr_expense_advance_clearing_trip/readme/CONTRIBUTORS.md @@ -0,0 +1,3 @@ +* Ledo + + * Don Kendall diff --git a/hr_expense_advance_clearing_trip/readme/DESCRIPTION.md b/hr_expense_advance_clearing_trip/readme/DESCRIPTION.md new file mode 100644 index 000000000..f922400a2 --- /dev/null +++ b/hr_expense_advance_clearing_trip/readme/DESCRIPTION.md @@ -0,0 +1,3 @@ +Links an employee **advance** to a **trip** so the trip's expenses can be +cleared against that advance in one step, rather than setting the advance on +each expense line individually. diff --git a/hr_expense_advance_clearing_trip/readme/USAGE.md b/hr_expense_advance_clearing_trip/readme/USAGE.md new file mode 100644 index 000000000..3875a51d2 --- /dev/null +++ b/hr_expense_advance_clearing_trip/readme/USAGE.md @@ -0,0 +1,4 @@ +On a trip, set the **Trip Advance** (an advance-type expense for the same +employee) and click **Apply Advance**. Every regular expense on the trip that +is not yet linked to an advance is cleared against the trip advance. The +**Advance Remaining** field shows the residual to clear. diff --git a/hr_expense_advance_clearing_trip/static/description/index.html b/hr_expense_advance_clearing_trip/static/description/index.html new file mode 100644 index 000000000..af19b16de --- /dev/null +++ b/hr_expense_advance_clearing_trip/static/description/index.html @@ -0,0 +1,450 @@ + + + + + +HR Expense Advance Clearing Trip + + + +
+ + + +Odoo Community Association + +
+

HR Expense Advance Clearing Trip

+ +

Alpha License: AGPL-3 OCA/hr-expense Translate me on Weblate Try me on Runboat

+

Links an employee advance to a trip so the trip’s expenses can +be cleared against that advance in one step, rather than setting the +advance on each expense line individually.

+
+

Important

+

This is an alpha version, the data model and design can change at any time without warning. +Only for development or testing purpose, do not use in production. +More details on development status

+
+

Table of contents

+ +
+

Usage

+

On a trip, set the Trip Advance (an advance-type expense for the +same employee) and click Apply Advance. Every regular expense on the +trip that is not yet linked to an advance is cleared against the trip +advance. The Advance Remaining field shows the residual to clear.

+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Ledo
  • +
+
+
+

Contributors

+
    +
  • Ledo
      +
    • Don Kendall
    • +
    +
  • +
+
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

Current maintainer:

+

dnplkndll

+

This module is part of the OCA/hr-expense project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+
+ + diff --git a/hr_expense_advance_clearing_trip/tests/__init__.py b/hr_expense_advance_clearing_trip/tests/__init__.py new file mode 100644 index 000000000..5c5933709 --- /dev/null +++ b/hr_expense_advance_clearing_trip/tests/__init__.py @@ -0,0 +1 @@ +from . import test_hr_expense_advance_clearing_trip diff --git a/hr_expense_advance_clearing_trip/tests/test_hr_expense_advance_clearing_trip.py b/hr_expense_advance_clearing_trip/tests/test_hr_expense_advance_clearing_trip.py new file mode 100644 index 000000000..966044f6d --- /dev/null +++ b/hr_expense_advance_clearing_trip/tests/test_hr_expense_advance_clearing_trip.py @@ -0,0 +1,75 @@ +# Copyright 2026 Ledo +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from datetime import date + +from odoo.exceptions import UserError +from odoo.tests import tagged + +from odoo.addons.hr_expense.tests.common import TestExpenseCommon + + +@tagged("-at_install", "post_install") +class TestHrExpenseAdvanceClearingTrip(TestExpenseCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + advance_account = cls.company_data["default_account_deferred_expense"] + advance_account.reconcile = True + cls.emp_advance = cls.env.ref("hr_expense_advance_clearing.product_emp_advance") + cls.emp_advance.property_account_expense_id = advance_account + cls.product_a.standard_price = 0 + cls.employee = cls.expense_employee + cls.trip = cls.env["hr.trip"].create( + { + "name": "Munich", + "start_date": date(2026, 2, 1), + "end_date": date(2026, 2, 7), + "employee_id": cls.employee.id, + } + ) + cls.advance = cls.env["hr.expense"].create( + { + "name": "Munich advance", + "employee_id": cls.employee.id, + "product_id": cls.emp_advance.id, + "total_amount_currency": 1000.0, + "expense_type": "advance", + "payment_mode": "own_account", + } + ) + cls.exp_a = cls.env["hr.expense"].create( + { + "name": "Hotel", + "employee_id": cls.employee.id, + "product_id": cls.product_a.id, + "total_amount_currency": 300.0, + "trip_id": cls.trip.id, + } + ) + cls.exp_b = cls.env["hr.expense"].create( + { + "name": "Meals", + "employee_id": cls.employee.id, + "product_id": cls.product_a.id, + "total_amount_currency": 120.0, + "trip_id": cls.trip.id, + } + ) + + def test_apply_advance_clears_trip_expenses(self): + self.trip.advance_id = self.advance + self.trip.action_apply_advance() + self.assertEqual(self.exp_a.clearing_advance_id, self.advance) + self.assertEqual(self.exp_b.clearing_advance_id, self.advance) + + def test_apply_advance_without_advance_raises(self): + self.trip.advance_id = False + with self.assertRaises(UserError): + self.trip.action_apply_advance() + + def test_apply_advance_skips_already_cleared(self): + self.trip.advance_id = self.advance + self.exp_a.clearing_advance_id = self.advance + self.trip.action_apply_advance() + self.assertEqual(self.exp_b.clearing_advance_id, self.advance) diff --git a/hr_expense_advance_clearing_trip/views/hr_trip_views.xml b/hr_expense_advance_clearing_trip/views/hr_trip_views.xml new file mode 100644 index 000000000..0f8d84f5e --- /dev/null +++ b/hr_expense_advance_clearing_trip/views/hr_trip_views.xml @@ -0,0 +1,28 @@ + + + + hr.trip.view.form - advance clearing + hr.trip + + + + + + + + + + + From d09452c1b51a570278a926f5d1f4580d5920e568 Mon Sep 17 00:00:00 2001 From: Don Kendall Date: Fri, 19 Jun 2026 12:35:53 -0400 Subject: [PATCH 75/75] [DEMO] trip suite: combined branch + SMB sales-rep walk narrative --- DEMO_trip_suite.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 DEMO_trip_suite.md diff --git a/DEMO_trip_suite.md b/DEMO_trip_suite.md new file mode 100644 index 000000000..66b1c07d7 --- /dev/null +++ b/DEMO_trip_suite.md @@ -0,0 +1,43 @@ +# HR Expense — Trip suite demo (SMB sales-rep walk) + +This branch stacks the optional **Trip** layer over the base hr-expense flow so it +can be installed + evaluated as one unit: + +- `hr_expense_trip` — the optional Trip container (groups expense lines; approval + workflow draft → request → approve → collect receipts → done; trip PDF). +- `hr_expense_advance_clearing` + `hr_expense_advance_clearing_trip` — a **trip + advance = the budget** (cash issued for the trip, cleared against actual spend). +- `hr_expense_payment` + `hr_expense_payment_trip` — **reimburse the whole trip + in one payment**. + +The Trip is **opt-in**: nothing changes about the plain per-expense flow unless a +trip is used. + +## Scenario — a sales rep on the road + +**Without a trip (base flow):** the rep files each expense individually (flight, +hotel, dinner). Each is approved and reimbursed on its own; there is no budget and +no grouping — fine for the occasional expense, noisy for a multi-day trip. + +**With a trip:** the rep's manager opens a **Trip** ("Q1 East Coast Sales Tour"), +issues a **$2,000 advance** as the trip budget, and approves the trip. The rep +collects receipts against the trip; **Apply Advance** clears them against the +budget (Advance Remaining tracks what's left), and **Pay Trip** reimburses any +balance in one payment. One approval, one budget, one settlement. + +| Need | Base | Trip | +|---|---|---| +| Group a multi-day trip's expenses | — | trip.expense_ids | +| Budget | — | advance ( $ issued up front ) | +| Approval | per expense | one trip approval workflow | +| Reimburse | per expense | one payment (Pay Trip) | +| Document | per expense | trip PDF report | + +## Screenshots +- Trip with advance budget + Apply Advance ([advance demo](https://storage.googleapis.com/ledo-pr-assets/hr-expense/pr-3/advance-clearing-trip.png)). +- Trip form + batched expenses + state machine ([trip foundation](https://storage.googleapis.com/ledo-pr-assets/hr-expense/pr-1/01-buffer-form-flow-index.png) — see hr-expense#1 for the trip walk). + +## Eval +Open this branch's runboat build (admin/admin) and follow the scenario above. +Note: in 19.0, expense posting routes through `hr.expense.post.wizard`, so for the +**Pay Trip** step do *Create Bill* → confirm the post wizard → **Pay Trip**.