diff --git a/hr_expense_payment/README.rst b/hr_expense_payment/README.rst index 52ae18d64..55d883dd9 100644 --- a/hr_expense_payment/README.rst +++ b/hr_expense_payment/README.rst @@ -32,11 +32,22 @@ HR Expense Payment |badge1| |badge2| |badge3| |badge4| |badge5| -This module links each employee expense to the payment(s) that settled -it. When you Register Payment on a posted employee-paid expense, the -resulting payment back-links to the source expense, and vice versa. A -post-install hook backfills the link for payments made before the module -was installed. +This module links each employee-paid expense to the payment(s) that +settled it, in both directions: ``hr.expense.payment_ids`` and +``account.payment.reconciled_expense_ids``. + +The link is computed from the reconciliation between the expense's +journal entry and the payment's journal items — the same way core +derives ``reconciled_bill_ids`` on payments. It therefore always +reflects the current accounting state (partial payments included), needs +no manual bookkeeping, and covers payments however they were registered +(expense's Register Payment button, the journal entry's own button, or +manual reconciliation). + +Odoo 19.0 core's ``account.payment.expense_ids`` only covers +company-paid expenses (whose journal entry *is* the payment's move); +this module covers the employee-reimbursement direction that core does +not track. **Table of contents** diff --git a/hr_expense_payment/__init__.py b/hr_expense_payment/__init__.py index e6c908b9c..31660d6a9 100644 --- a/hr_expense_payment/__init__.py +++ b/hr_expense_payment/__init__.py @@ -1,5 +1,3 @@ # License AGPL-3.0 or later (https://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 index 2b60855be..e00845fc3 100644 --- a/hr_expense_payment/__manifest__.py +++ b/hr_expense_payment/__manifest__.py @@ -4,7 +4,7 @@ { "name": "HR Expense Payment", - "version": "18.0.1.0.0", + "version": "19.0.1.0.0", "category": "Human Resources", "author": "Tecnativa, Ecosoft, Odoo Community Association (OCA)", "license": "AGPL-3", @@ -12,5 +12,4 @@ "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 deleted file mode 100644 index cdd39fb54..000000000 --- a/hr_expense_payment/hooks.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright 2019 Tecnativa - Ernesto Tejeda -# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). - - -def post_init_hook(env): - """Trying to fill the source expense sheet in payments""" - sheets = env["hr.expense.sheet"].search([("payment_mode", "=", "own_account")]) - for sheet in sheets: - amls = sheet.account_move_ids.mapped("line_ids") - reconcile = amls.mapped("full_reconcile_id") - aml_payment = reconcile.mapped("reconciled_line_ids").filtered( - lambda r, amls=amls: 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 index c7667a3fa..82e1cb11c 100644 --- a/hr_expense_payment/models/__init__.py +++ b/hr_expense_payment/models/__init__.py @@ -1,4 +1,4 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from . import account_payment -from . import hr_expense_sheet +from . import hr_expense diff --git a/hr_expense_payment/models/account_payment.py b/hr_expense_payment/models/account_payment.py index 0edcaea31..e4ca55054 100644 --- a/hr_expense_payment/models/account_payment.py +++ b/hr_expense_payment/models/account_payment.py @@ -2,18 +2,37 @@ # 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 +from odoo import api, 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, + # Core's own expense_ids (related to move_id.expense_ids) only covers + # company-paid expenses; this covers the reimbursement direction, derived + # from reconciliation like core's reconciled_bill_ids. + reconciled_expense_ids = fields.Many2many( + comodel_name="hr.expense", + string="Reimbursed Expenses", + compute="_compute_reconciled_expense_ids", + compute_sudo=True, + search="_search_reconciled_expense_ids", + help="Employee-paid expenses whose journal items have been reconciled " + "with this payment.", ) + + @api.depends( + "move_id.line_ids.matched_debit_ids", + "move_id.line_ids.matched_credit_ids", + ) + def _compute_reconciled_expense_ids(self): + for payment in self: + payment.reconciled_expense_ids = ( + payment.move_id._get_reconciled_amls().move_id.expense_ids + ) + + def _search_reconciled_expense_ids(self, operator, value): + if operator not in ("in", "="): + return NotImplemented + expenses = self.env["hr.expense"].browse(value) + return [("id", "in", expenses.payment_ids.ids)] diff --git a/hr_expense_payment/models/hr_expense.py b/hr_expense_payment/models/hr_expense.py new file mode 100644 index 000000000..71fa8a625 --- /dev/null +++ b/hr_expense_payment/models/hr_expense.py @@ -0,0 +1,35 @@ +# 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 api, fields, models + + +class HrExpense(models.Model): + _inherit = "hr.expense" + + payment_ids = fields.Many2many( + comodel_name="account.payment", + string="Payments", + compute="_compute_payment_ids", + # Employees read their expenses without accounting access; the + # reconciliation walk needs sudo. + compute_sudo=True, + search="_search_payment_ids", + help="Payments whose journal items have been reconciled with this " + "expense's journal entry.", + ) + + @api.depends( + "account_move_id.line_ids.matched_debit_ids", + "account_move_id.line_ids.matched_credit_ids", + ) + def _compute_payment_ids(self): + for expense in self: + expense.payment_ids = expense.account_move_id._get_reconciled_payments() + + def _search_payment_ids(self, operator, value): + if operator not in ("in", "="): + return NotImplemented + payments = self.env["account.payment"].browse(value) + return [("id", "in", payments.reconciled_expense_ids.ids)] diff --git a/hr_expense_payment/models/hr_expense_sheet.py b/hr_expense_payment/models/hr_expense_sheet.py deleted file mode 100644 index 59462a687..000000000 --- a/hr_expense_payment/models/hr_expense_sheet.py +++ /dev/null @@ -1,26 +0,0 @@ -# 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 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/DESCRIPTION.md b/hr_expense_payment/readme/DESCRIPTION.md index 430a28ba0..a68f4c126 100644 --- a/hr_expense_payment/readme/DESCRIPTION.md +++ b/hr_expense_payment/readme/DESCRIPTION.md @@ -1,4 +1,14 @@ -This module links each employee expense to the payment(s) that settled it. -When you Register Payment on a posted employee-paid expense, the resulting -payment back-links to the source expense, and vice versa. A post-install -hook backfills the link for payments made before the module was installed. +This module links each employee-paid expense to the payment(s) that settled +it, in both directions: `hr.expense.payment_ids` and +`account.payment.reconciled_expense_ids`. + +The link is computed from the reconciliation between the expense's journal +entry and the payment's journal items — the same way core derives +`reconciled_bill_ids` on payments. It therefore always reflects the current +accounting state (partial payments included), needs no manual bookkeeping, +and covers payments however they were registered (expense's Register +Payment button, the journal entry's own button, or manual reconciliation). + +Odoo 19.0 core's `account.payment.expense_ids` only covers company-paid +expenses (whose journal entry *is* the payment's move); this module covers +the employee-reimbursement direction that core does not track. diff --git a/hr_expense_payment/static/description/index.html b/hr_expense_payment/static/description/index.html index 55c55914b..9bdcda6cf 100644 --- a/hr_expense_payment/static/description/index.html +++ b/hr_expense_payment/static/description/index.html @@ -3,7 +3,7 @@
-