From 4261b6c53c2a083d2fdaee256b3ddcb431e7ddd6 Mon Sep 17 00:00:00 2001 From: Suherdy Yacob Date: Mon, 6 Apr 2026 12:14:56 +0700 Subject: [PATCH] refactor: simplify expense lock bypass using context flag and direct base method calls --- models/account_payment.py | 69 ++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/models/account_payment.py b/models/account_payment.py index 0e7feff..385f9d7 100644 --- a/models/account_payment.py +++ b/models/account_payment.py @@ -1,5 +1,8 @@ from odoo import fields, models, api, _ from odoo.exceptions import UserError +import logging + +_logger = logging.getLogger(__name__) class AccountPayment(models.Model): _inherit = 'account.payment' @@ -8,46 +11,44 @@ class AccountPayment(models.Model): def action_post(self): """ - Overriding to bypass hr_expense restriction and ensure deductions are synced. - We temporarily clear the link in the DB to avoid the 'Invalid Operation' error. + Confirmation bypass. Calls standard post with skip flag. """ - for payment in self: - if payment.expense_sheet_id and payment.state == 'draft': - sheet_id = payment.expense_sheet_id.id - # 1. Clear the link in DB and Cache (Skip sync to avoid recursion) - payment.with_context(skip_account_move_synchronization=True).write({'expense_sheet_id': False}) - - try: - # 2. Force Sync Deductions while 'unlinked' - payment._synchronize_to_moves({'amount', 'deduction_line_ids', 'amount_substract'}) - - # 3. Call standard post - res = super(AccountPayment, payment).action_post() - finally: - # 4. Restore the link - payment.with_context(skip_account_move_synchronization=True).write({'expense_sheet_id': sheet_id}) - else: - super(AccountPayment, payment).action_post() - return True + return super(AccountPayment, self.with_context(skip_expense_lock=True)).action_post() def _synchronize_to_moves(self, changed_fields): - # Trigger deduction sync if needed - if 'deduction_line_ids' in changed_fields or 'amount_substract' in changed_fields: - if 'amount' not in changed_fields: - changed_fields = set(changed_fields) | {'amount'} - - # Bypass for manual edits - for payment in self: - if payment.expense_sheet_id and payment.state == 'draft': - sheet_id = payment.expense_sheet_id.id - payment.with_context(skip_account_move_synchronization=True).write({'expense_sheet_id': False}) - try: - return super(AccountPayment, payment)._synchronize_to_moves(changed_fields) - finally: - payment.with_context(skip_account_move_synchronization=True).write({'expense_sheet_id': sheet_id}) + # Always allow sync if bypass flag is set + if self._context.get('skip_expense_lock'): + # Force the refresh by ensuring 'amount' is in fields + if 'deduction_line_ids' in changed_fields or 'amount_substract' in changed_fields: + if 'amount' not in changed_fields: + changed_fields = set(changed_fields) | {'amount'} + + # SURGICAL BYPASS: + # We call the base 'account.payment' method directly, skipping the 'hr_expense' override + # that raises the UserError. + from odoo.addons.account.models.account_payment import AccountPayment as AccountPaymentBase + return AccountPaymentBase._synchronize_to_moves(self, changed_fields) + # Trigger deduction sync for manual edits (write) if not locked + if 'deduction_line_ids' in changed_fields or 'amount_substract' in changed_fields: + if not self.expense_sheet_id: + if 'amount' not in changed_fields: + changed_fields = set(changed_fields) | {'amount'} + return super()._synchronize_to_moves(changed_fields) + def _synchronize_from_moves(self, changed_fields): + if self._context.get('skip_expense_lock'): + from odoo.addons.account.models.account_payment import AccountPayment as AccountPaymentBase + return AccountPaymentBase._synchronize_from_moves(self, changed_fields) + return super()._synchronize_from_moves(changed_fields) + + def write(self, vals): + if 'deduction_line_ids' in vals or 'amount_substract' in vals: + # Force trigger sync with bypass flag + return super(AccountPayment, self.with_context(skip_expense_lock=True)).write(vals) + return super().write(vals) + def action_cancel(self): res = super().action_cancel() for payment in self: