From 15aaf1aeab03cadcccc96a8f2270608632bff61f Mon Sep 17 00:00:00 2001 From: ernesto Date: Mon, 11 Feb 2019 09:03:17 -0500 Subject: [PATCH 01/25] hr_expense_cancel: lets to cancel and correct expenses This module lets to cancel and correct expenses. It adds a cancel button on the expense sheet that undo reconciliations and delete payments and journal entries. --- hr_expense_cancel/README.rst | 105 ++++ hr_expense_cancel/__init__.py | 5 + hr_expense_cancel/__manifest__.py | 21 + hr_expense_cancel/hooks.py | 20 + hr_expense_cancel/i18n/es.po | 42 ++ hr_expense_cancel/models/__init__.py | 4 + hr_expense_cancel/models/account_payment.py | 13 + hr_expense_cancel/models/hr_expense.py | 63 +++ hr_expense_cancel/readme/CONTRIBUTORS.rst | 4 + hr_expense_cancel/readme/DESCRIPTION.rst | 3 + hr_expense_cancel/readme/INSTALL.rst | 5 + hr_expense_cancel/readme/USAGE.rst | 14 + .../static/description/index.html | 453 ++++++++++++++++++ hr_expense_cancel/tests/__init__.py | 3 + .../tests/test_hr_expense_cancel.py | 155 ++++++ hr_expense_cancel/views/hr_expense_views.xml | 17 + hr_expense_cancel/wizard/__init__.py | 3 + .../hr_expense_sheet_register_payment.py | 16 + 18 files changed, 946 insertions(+) create mode 100644 hr_expense_cancel/README.rst create mode 100644 hr_expense_cancel/__init__.py create mode 100644 hr_expense_cancel/__manifest__.py create mode 100644 hr_expense_cancel/hooks.py create mode 100644 hr_expense_cancel/i18n/es.po create mode 100644 hr_expense_cancel/models/__init__.py create mode 100644 hr_expense_cancel/models/account_payment.py create mode 100644 hr_expense_cancel/models/hr_expense.py create mode 100644 hr_expense_cancel/readme/CONTRIBUTORS.rst create mode 100644 hr_expense_cancel/readme/DESCRIPTION.rst create mode 100644 hr_expense_cancel/readme/INSTALL.rst create mode 100644 hr_expense_cancel/readme/USAGE.rst create mode 100644 hr_expense_cancel/static/description/index.html create mode 100644 hr_expense_cancel/tests/__init__.py create mode 100644 hr_expense_cancel/tests/test_hr_expense_cancel.py create mode 100644 hr_expense_cancel/views/hr_expense_views.xml create mode 100644 hr_expense_cancel/wizard/__init__.py create mode 100644 hr_expense_cancel/wizard/hr_expense_sheet_register_payment.py diff --git a/hr_expense_cancel/README.rst b/hr_expense_cancel/README.rst new file mode 100644 index 000000000..17eec8d3a --- /dev/null +++ b/hr_expense_cancel/README.rst @@ -0,0 +1,105 @@ +================= +Hr expense cancel +================= + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! 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/11.0/hr_expense_cancel + :alt: OCA/hr +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/hr-11-0/hr-11-0-hr_expense_cancel + :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/11.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows you to cancel and correct expenses. It adds a cancel button +on the expense sheet that undo reconciliations and delete payments and journal +entries. + +**Table of contents** + +.. contents:: + :local: + +Installation +============ + +It's assumed that for each expense sheet, the *payments* that are reconciled +with their *journal entry* were created and reconciled by the *Expense Tracker* +module automatically. Therefore, when this module is installed, these payments +will automatically be associated with said expense sheet so that they can be +deleted in case the expense sheet is canceled. + +Usage +===== + +To use this module, you need to: + +#. Go to *Expenses -> My Expenses -> Expenses to Submit* and create a new + *Expense* with *Employee (to reimburse)* checked on the field *Payment By* +#. Click on *Submit to Manager* button +#. Click on *Approve* button +#. Click on *Post Journal Entries* button +#. Click on *Register Payment* button, fill in the data of the wizard and + click on *Validate* button +#. After that, the *Expense report* will have an associated journal entry + reconciled with a payment +#. Click on *Cancel* button +#. The *Expense report* will be set to *Submitted* state; the journal entry and + the payment will be deleted + +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 +~~~~~~~ + +* Tecnativa + +Contributors +~~~~~~~~~~~~ + +* `Tecnativa `_: + + * Pedro M. Baeza + * Ernesto Tejeda + +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_cancel/__init__.py b/hr_expense_cancel/__init__.py new file mode 100644 index 000000000..4f890c3e9 --- /dev/null +++ b/hr_expense_cancel/__init__.py @@ -0,0 +1,5 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import models +from . import wizard +from .hooks import post_init_hook diff --git a/hr_expense_cancel/__manifest__.py b/hr_expense_cancel/__manifest__.py new file mode 100644 index 000000000..0bfabd2be --- /dev/null +++ b/hr_expense_cancel/__manifest__.py @@ -0,0 +1,21 @@ +# Copyright 2019 Tecnativa - Ernesto Tejeda +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +{ + "name": "Hr expense cancel", + "version": "11.0.1.0.0", + "author": "Tecnativa, " + "Odoo Community Association (OCA)", + "website": "https://github.com/OCA/hr", + "license": "AGPL-3", + "category": "Human Resources", + "depends": [ + 'hr_expense', + 'account_cancel', + ], + 'data': [ + "views/hr_expense_views.xml", + ], + "post_init_hook": "post_init_hook", + "installable": True, +} diff --git a/hr_expense_cancel/hooks.py b/hr_expense_cancel/hooks.py new file mode 100644 index 000000000..d5654c36f --- /dev/null +++ b/hr_expense_cancel/hooks.py @@ -0,0 +1,20 @@ +# Copyright 2019 Tecnativa - Ernesto Tejeda +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import api, SUPERUSER_ID + + +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, {}) + + for sheet in env['hr.expense.sheet'].search([]): + expense_lines = sheet.expense_line_ids + if any(exp.payment_mode == 'own_account' for exp in expense_lines): + amls = sheet.account_move_id.mapped('line_ids') + reconcile = amls.mapped('full_reconcile_id') + aml_payment = reconcile.reconciled_line_ids.filtered( + lambda r: r not in amls) + payment = aml_payment.mapped('payment_id') + payment.write({'expense_sheet_id': sheet.id}) diff --git a/hr_expense_cancel/i18n/es.po b/hr_expense_cancel/i18n/es.po new file mode 100644 index 000000000..82c6d9853 --- /dev/null +++ b/hr_expense_cancel/i18n/es.po @@ -0,0 +1,42 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * hr_expense_cancel +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 11.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-02-15 06:58+0000\n" +"PO-Revision-Date: 2019-02-15 06:58+0000\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_cancel +#: model:ir.ui.view,arch_db:hr_expense_cancel.view_hr_expense_sheet_form +msgid "Cancel" +msgstr "Cancelar" + +#. module: hr_expense_cancel +#: model:ir.model,name:hr_expense_cancel.model_hr_expense_sheet +msgid "Expense Report" +msgstr "Nota de gastos" + +#. module: hr_expense_cancel +#: model:ir.model,name:hr_expense_cancel.model_hr_expense_sheet_register_payment_wizard +msgid "Expense Report Register Payment wizard" +msgstr "Asistente registro pago de nota de gastos" + +#. module: hr_expense_cancel +#: model:ir.model.fields,field_description:hr_expense_cancel.field_account_payment_expense_sheet_id +msgid "Expense sheet" +msgstr "Nota de gastos" + +#. module: hr_expense_cancel +#: model:ir.model,name:hr_expense_cancel.model_account_payment +msgid "Payments" +msgstr "Pagos" + diff --git a/hr_expense_cancel/models/__init__.py b/hr_expense_cancel/models/__init__.py new file mode 100644 index 000000000..bd48771a7 --- /dev/null +++ b/hr_expense_cancel/models/__init__.py @@ -0,0 +1,4 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import hr_expense +from . import account_payment diff --git a/hr_expense_cancel/models/account_payment.py b/hr_expense_cancel/models/account_payment.py new file mode 100644 index 000000000..66bede576 --- /dev/null +++ b/hr_expense_cancel/models/account_payment.py @@ -0,0 +1,13 @@ +# Copyright 2019 Tecnativa - Ernesto Tejeda +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import models, fields + + +class AccountPayment(models.Model): + _inherit = "account.payment" + + expense_sheet_id = fields.Many2one( + comodel_name="hr.expense.sheet", + string="Expense sheet", + ) diff --git a/hr_expense_cancel/models/hr_expense.py b/hr_expense_cancel/models/hr_expense.py new file mode 100644 index 000000000..8bffac036 --- /dev/null +++ b/hr_expense_cancel/models/hr_expense.py @@ -0,0 +1,63 @@ +# Copyright 2019 Tecnativa - Ernesto Tejeda +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import api, models + + +class HrExpenseSheet(models.Model): + _inherit = "hr.expense.sheet" + + @api.multi + def action_cancel(self): + for sheet in self: + account_move = sheet.account_move_id + sheet.account_move_id = False + # If the sheet is paid then remove payments + if sheet.state == 'done': + payments = self.env['account.payment'].search([ + ('expense_sheet_id', '=', sheet.id), + ('state', '!=', 'cancelled'), + ]) + if sheet.expense_line_ids[:1].payment_mode == 'own_account': + self._remove_move_reconcile(payments, account_move) + self._cancel_payments(payments) + else: + # In this case, during the cancellation the journal entry + # will be deleted + self._cancel_payments(payments) + payments.unlink() + # Deleting the Journal entry if in the previous steps + # (if the expense sheet is paid and payment_mode == 'own_account') + # it has not been deleted + if account_move.exists(): + if account_move.state != 'draft': + account_move.button_cancel() + account_move.unlink() + sheet.state = 'submit' + + @api.multi + def action_sheet_move_create(self): + res = super(HrExpenseSheet, self).action_sheet_move_create() + if self.expense_line_ids[0].payment_mode == 'company_account': + self.account_move_id.mapped('line_ids.payment_id').write({ + 'expense_sheet_id': self.id, + }) + return res + + def _remove_move_reconcile(self, payments, account_move): + """Delete only reconciliations made with the payments generated + by hr_expense module automatically""" + reconcile = account_move.mapped('line_ids.full_reconcile_id') + + payments_aml = payments.mapped('move_line_ids') + aml_unreconcile = payments_aml.filtered( + lambda r: r.full_reconcile_id in reconcile) + + aml_unreconcile.remove_move_reconcile() + + def _cancel_payments(self, payments): + for rec in payments: + for move in rec.move_line_ids.mapped('move_id'): + move.button_cancel() + move.unlink() + rec.state = 'cancelled' diff --git a/hr_expense_cancel/readme/CONTRIBUTORS.rst b/hr_expense_cancel/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..29c1846f5 --- /dev/null +++ b/hr_expense_cancel/readme/CONTRIBUTORS.rst @@ -0,0 +1,4 @@ +* `Tecnativa `_: + + * Pedro M. Baeza + * Ernesto Tejeda diff --git a/hr_expense_cancel/readme/DESCRIPTION.rst b/hr_expense_cancel/readme/DESCRIPTION.rst new file mode 100644 index 000000000..e5e47f79c --- /dev/null +++ b/hr_expense_cancel/readme/DESCRIPTION.rst @@ -0,0 +1,3 @@ +This module allows you to cancel and correct expenses. It adds a cancel button +on the expense sheet that undo reconciliations and delete payments and journal +entries. diff --git a/hr_expense_cancel/readme/INSTALL.rst b/hr_expense_cancel/readme/INSTALL.rst new file mode 100644 index 000000000..22116b5be --- /dev/null +++ b/hr_expense_cancel/readme/INSTALL.rst @@ -0,0 +1,5 @@ +It's assumed that for each expense sheet, the *payments* that are reconciled +with their *journal entry* were created and reconciled by the *Expense Tracker* +module automatically. Therefore, when this module is installed, these payments +will automatically be associated with said expense sheet so that they can be +deleted in case the expense sheet is canceled. diff --git a/hr_expense_cancel/readme/USAGE.rst b/hr_expense_cancel/readme/USAGE.rst new file mode 100644 index 000000000..779337669 --- /dev/null +++ b/hr_expense_cancel/readme/USAGE.rst @@ -0,0 +1,14 @@ +To use this module, you need to: + +#. Go to *Expenses -> My Expenses -> Expenses to Submit* and create a new + *Expense* with *Employee (to reimburse)* checked on the field *Payment By* +#. Click on *Submit to Manager* button +#. Click on *Approve* button +#. Click on *Post Journal Entries* button +#. Click on *Register Payment* button, fill in the data of the wizard and + click on *Validate* button +#. After that, the *Expense report* will have an associated journal entry + reconciled with a payment +#. Click on *Cancel* button +#. The *Expense report* will be set to *Submitted* state; the journal entry and + the payment will be deleted diff --git a/hr_expense_cancel/static/description/index.html b/hr_expense_cancel/static/description/index.html new file mode 100644 index 000000000..5900fa306 --- /dev/null +++ b/hr_expense_cancel/static/description/index.html @@ -0,0 +1,453 @@ + + + + + + +Hr expense cancel + + + +
+

Hr expense cancel

+ + +

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

+

This module allows you to cancel and correct expenses. It adds a cancel button +on the expense sheet that undo reconciliations and delete payments and journal +entries.

+

Table of contents

+ +
+

Installation

+

It’s assumed that for each expense sheet, the payments that are reconciled +with their journal entry were created and reconciled by the Expense Tracker +module automatically. Therefore, when this module is installed, these payments +will automatically be associated with said expense sheet so that they can be +deleted in case the expense sheet is canceled.

+
+
+

Usage

+

To use this module, you need to:

+
    +
  1. Go to Expenses -> My Expenses -> Expenses to Submit and create a new +Expense with Employee (to reimburse) checked on the field Payment By
  2. +
  3. Click on Submit to Manager button
  4. +
  5. Click on Approve button
  6. +
  7. Click on Post Journal Entries button
  8. +
  9. Click on Register Payment button, fill in the data of the wizard and +click on Validate button
  10. +
  11. After that, the Expense report will have an associated journal entry +reconciled with a payment
  12. +
  13. Click on Cancel button
  14. +
  15. The Expense report will be set to Submitted state; the journal entry and +the payment will be deleted
  16. +
+
+
+

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

+
    +
  • Tecnativa
  • +
+
+
+

Contributors

+
    +
  • Tecnativa:
      +
    • Pedro M. Baeza
    • +
    • Ernesto Tejeda
    • +
    +
  • +
+
+
+

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_cancel/tests/__init__.py b/hr_expense_cancel/tests/__init__.py new file mode 100644 index 000000000..9533dd6a9 --- /dev/null +++ b/hr_expense_cancel/tests/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import test_hr_expense_cancel diff --git a/hr_expense_cancel/tests/test_hr_expense_cancel.py b/hr_expense_cancel/tests/test_hr_expense_cancel.py new file mode 100644 index 000000000..0efb693b6 --- /dev/null +++ b/hr_expense_cancel/tests/test_hr_expense_cancel.py @@ -0,0 +1,155 @@ +# Copyright 2019 Tecnativa - Ernesto Tejeda +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import odoo.tests.common as common +from odoo.exceptions import UserError +from ..hooks import post_init_hook + + +class TestHrExpenseCancel(common.TransactionCase): + + def setUp(self): + super(TestHrExpenseCancel, self).setUp() + self.payment_obj = self.env['account.payment'] + self.payment_journal = self.env['account.journal'].search( + [('type', 'in', ['cash', 'bank'])], limit=1) + self.payment_journal.update_posted = True + + self.main_company = 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, + 'update_posted': True, + }) + + self.expense_sheet = self.env['hr.expense.sheet'].create({ + 'employee_id': self.ref("hr.employee_root"), + '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_root"), + 'product_id': self.ref('hr_expense.hotel_rent'), + 'unit_amount': 1, + 'quantity': 10, + 'sheet_id': self.expense_sheet.id, + }) + self.expense._onchange_product_id() + + def _get_payment_wizard(self): + ctx = dict(active_ids=self.expense_sheet.ids) + wizard_obj = self.env['hr.expense.sheet.register.payment.wizard'] + p_methods = self.payment_journal.outbound_payment_method_ids + return wizard_obj.with_context(ctx).create({ + 'journal_id': self.payment_journal.id, + 'amount': self.expense_sheet.total_amount, + 'payment_method_id': p_methods and p_methods[0].id or False, + 'payment_type': 'inbound', + }) + + def test_post_init_hook(self): + self.expense_sheet.action_sheet_move_create() + payment_wizard = self._get_payment_wizard() + payment_wizard.expense_post_payment() + + payment = self.payment_obj.search( + [('expense_sheet_id', '=', self.expense_sheet.id)]) + self.assertEqual(len(payment), 1) + + payment.expense_sheet_id = False + + payment = self.payment_obj.search( + [('expense_sheet_id', '=', self.expense_sheet.id)]) + self.assertFalse(payment) + + post_init_hook(self.env.cr, self.registry) + + payment = self.payment_obj.search( + [('expense_sheet_id', '=', self.expense_sheet.id)]) + self.assertEqual(len(payment), 1) + + def test_get_payment_vals(self): + self.expense_sheet.action_sheet_move_create() + + payment_wizard = self._get_payment_wizard() + + payment = self.payment_obj.search( + [('expense_sheet_id', '=', self.expense_sheet.id)]) + self.assertFalse(payment) + + payment_wizard.expense_post_payment() + payment = self.payment_obj.search( + [('expense_sheet_id', '=', self.expense_sheet.id)]) + self.assertEqual(len(payment), 1) + + def test_action_sheet_move_create(self): + self.expense.payment_mode = 'company_account' + + payment = self.payment_obj.search( + [('expense_sheet_id', '=', self.expense_sheet.id)]) + self.assertFalse(payment) + + self.expense_sheet.action_sheet_move_create() + payment = self.payment_obj.search( + [('expense_sheet_id', '=', self.expense_sheet.id)]) + self.assertEqual(len(payment), 1) + + def test_action_cancel_posted(self): + self.expense_sheet.action_sheet_move_create() + + payment = self.payment_obj.search( + [('expense_sheet_id', '=', self.expense_sheet.id)]) + self.assertFalse(len(payment), 1) + self.assertTrue(self.expense_sheet.account_move_id) + + self.expense_sheet.action_cancel() + payment = self.payment_obj.search( + [('expense_sheet_id', '=', self.expense_sheet.id)]) + self.assertFalse(payment) + self.assertFalse(self.expense_sheet.account_move_id) + + def test_action_cancel_no_update_posted(self): + journals = self.payment_journal | self.expense_journal + journals.write({'update_posted': False}) + with self.assertRaises(UserError): + self.test_action_cancel_company_account() + with self.assertRaises(UserError): + self.test_action_cancel_own_account() + + def test_action_cancel_company_account(self): + self.expense.payment_mode = 'company_account' + self.expense_sheet.action_sheet_move_create() + + payment = self.payment_obj.search( + [('expense_sheet_id', '=', self.expense_sheet.id)]) + self.assertEqual(len(payment), 1) + self.assertTrue(self.expense_sheet.account_move_id) + + self.expense_sheet.action_cancel() + payment = self.payment_obj.search( + [('expense_sheet_id', '=', self.expense_sheet.id)]) + self.assertFalse(payment) + self.assertFalse(self.expense_sheet.account_move_id) + + def test_action_cancel_own_account(self): + self.expense_sheet.action_sheet_move_create() + + payment_wizard = self._get_payment_wizard() + payment_wizard.expense_post_payment() + + payment = self.payment_obj.search( + [('expense_sheet_id', '=', self.expense_sheet.id)]) + self.assertEqual(len(payment), 1) + self.assertTrue(self.expense_sheet.account_move_id) + + self.expense_sheet.action_cancel() + payment = self.payment_obj.search( + [('expense_sheet_id', '=', self.expense_sheet.id)]) + self.assertEqual(len(payment), 1) + self.assertEqual(payment.state, 'cancelled') + self.assertFalse(self.expense_sheet.account_move_id) diff --git a/hr_expense_cancel/views/hr_expense_views.xml b/hr_expense_cancel/views/hr_expense_views.xml new file mode 100644 index 000000000..70f910479 --- /dev/null +++ b/hr_expense_cancel/views/hr_expense_views.xml @@ -0,0 +1,17 @@ + + + + + + hr.expense.sheet.form.inherit + hr.expense.sheet + + + +