diff --git a/models/hr_expense_sheet.py b/models/hr_expense_sheet.py index f5aa95b..9fdc3cd 100644 --- a/models/hr_expense_sheet.py +++ b/models/hr_expense_sheet.py @@ -137,6 +137,82 @@ class HrExpenseSheet(models.Model): else: sheet.receipt_status = 'pending' + @api.depends('account_move_ids.payment_state', 'account_move_ids.amount_residual', 'account_move_ids.state') + def _compute_from_account_move_ids(self): + """ + Overriding to fix the 'IN PAYMENT' ribbon issue. + Standard Odoo assumes 'paid' if any move exists for company_account. + We check if the moves are actually in 'posted' state. + """ + for sheet in self: + if sheet.payment_mode == 'company_account': + if sheet.account_move_ids: + # Filter for moves that are NOT canceled + active_moves = sheet.account_move_ids.filtered(lambda m: m.state == 'posted') + if active_moves: + # If there are active moves that are not reversed + if active_moves - active_moves.filtered('reversal_move_id'): + sheet.payment_state = 'paid' + sheet.amount_residual = 0. + else: + sheet.payment_state = 'reversed' + sheet.amount_residual = sum(sheet.account_move_ids.mapped('amount_residual')) + else: + # Moves exist but none are 'posted' (e.g. they are all 'cancel' or 'draft') + sheet.payment_state = 'not_paid' + sheet.amount_residual = sum(sheet.account_move_ids.mapped('amount_residual')) + else: + sheet.payment_state = 'not_paid' + sheet.amount_residual = 0.0 + else: + # Standard Odoo logic for own_account + if sheet.account_move_ids: + sheet.amount_residual = sum(sheet.account_move_ids.mapped('amount_residual')) + sheet.payment_state = sheet.account_move_ids[:1].payment_state + else: + sheet.amount_residual = 0.0 + sheet.payment_state = 'not_paid' + + def _do_refuse(self, reason): + """ + Bypass the standard Odoo lock: 'You cannot cancel an expense sheet linked to a journal entry'. + We allow it but we'll try to cancel the moves first. + """ + self._do_reverse_moves() + # Explicitly call the original _do_refuse but WITHOUT the check, + # but since we already reversed/deleted moves, the original check won't trigger. + return super()._do_refuse(reason) + + def _do_reverse_moves(self): + """ + Overriding to handle account.payment explicitly. + Odoo's _do_reverse_moves calls _reverse_moves, which fails for payments. + """ + self = self.with_context(clean_context(self.env.context)) + moves = self.account_move_ids + if moves: + for sheet in self: + # Handle payments linked to this sheet + payments = sheet.account_move_ids.mapped('payment_id') + if payments: + # Cancel the payments directly + for payment in payments: + if payment.state == 'posted': + payment.action_cancel() + elif payment.state == 'draft': + payment.action_cancel() + + # Standard reversal for non-payment moves (if any) + non_payment_moves = sheet.account_move_ids.filtered(lambda m: not m.payment_id) + if non_payment_moves: + non_payment_moves._reverse_moves( + default_values_list=[{'invoice_date': fields.Date.context_today(move), 'ref': False} for move in non_payment_moves], + cancel=True + ) + + # Unlink draft moves (including payment moves that are now draft/cancel) + sheet.account_move_ids.filtered(lambda m: m.state == 'draft').unlink() + def action_reset_expense_sheets(self): """ Overriding reset to handle realizations.