diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/account/wizard/account_move_reversal.py | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/account/wizard/account_move_reversal.py')
| -rw-r--r-- | addons/account/wizard/account_move_reversal.py | 129 |
1 files changed, 129 insertions, 0 deletions
diff --git a/addons/account/wizard/account_move_reversal.py b/addons/account/wizard/account_move_reversal.py new file mode 100644 index 00000000..7724ca44 --- /dev/null +++ b/addons/account/wizard/account_move_reversal.py @@ -0,0 +1,129 @@ +# -*- coding: utf-8 -*- +from odoo import models, fields, api +from odoo.tools.translate import _ +from odoo.exceptions import UserError + + +class AccountMoveReversal(models.TransientModel): + """ + Account move reversal wizard, it cancel an account move by reversing it. + """ + _name = 'account.move.reversal' + _description = 'Account Move Reversal' + _check_company_auto = True + + move_ids = fields.Many2many('account.move', 'account_move_reversal_move', 'reversal_id', 'move_id', domain=[('state', '=', 'posted')]) + new_move_ids = fields.Many2many('account.move', 'account_move_reversal_new_move', 'reversal_id', 'new_move_id') + date_mode = fields.Selection(selection=[ + ('custom', 'Specific'), + ('entry', 'Journal Entry Date') + ], required=True, default='custom') + date = fields.Date(string='Reversal date', default=fields.Date.context_today) + reason = fields.Char(string='Reason') + refund_method = fields.Selection(selection=[ + ('refund', 'Partial Refund'), + ('cancel', 'Full Refund'), + ('modify', 'Full refund and new draft invoice') + ], string='Credit Method', required=True, + help='Choose how you want to credit this invoice. You cannot "modify" nor "cancel" if the invoice is already reconciled.') + journal_id = fields.Many2one('account.journal', string='Use Specific Journal', help='If empty, uses the journal of the journal entry to be reversed.', check_company=True) + company_id = fields.Many2one('res.company', required=True, readonly=True) + + # computed fields + residual = fields.Monetary(compute="_compute_from_moves") + currency_id = fields.Many2one('res.currency', compute="_compute_from_moves") + move_type = fields.Char(compute="_compute_from_moves") + + @api.model + def default_get(self, fields): + res = super(AccountMoveReversal, self).default_get(fields) + move_ids = self.env['account.move'].browse(self.env.context['active_ids']) if self.env.context.get('active_model') == 'account.move' else self.env['account.move'] + + if any(move.state != "posted" for move in move_ids): + raise UserError(_('You can only reverse posted moves.')) + if 'company_id' in fields: + res['company_id'] = move_ids.company_id.id or self.env.company.id + if 'move_ids' in fields: + res['move_ids'] = [(6, 0, move_ids.ids)] + if 'refund_method' in fields: + res['refund_method'] = (len(move_ids) > 1 or move_ids.move_type == 'entry') and 'cancel' or 'refund' + return res + + @api.depends('move_ids') + def _compute_from_moves(self): + for record in self: + move_ids = record.move_ids._origin + record.residual = len(move_ids) == 1 and move_ids.amount_residual or 0 + record.currency_id = len(move_ids.currency_id) == 1 and move_ids.currency_id or False + record.move_type = move_ids.move_type if len(move_ids) == 1 else (any(move.move_type in ('in_invoice', 'out_invoice') for move in move_ids) and 'some_invoice' or False) + + def _prepare_default_reversal(self, move): + reverse_date = self.date if self.date_mode == 'custom' else move.date + return { + 'ref': _('Reversal of: %(move_name)s, %(reason)s', move_name=move.name, reason=self.reason) + if self.reason + else _('Reversal of: %s', move.name), + 'date': reverse_date, + 'invoice_date': move.is_invoice(include_receipts=True) and (self.date or move.date) or False, + 'journal_id': self.journal_id and self.journal_id.id or move.journal_id.id, + 'invoice_payment_term_id': None, + 'invoice_user_id': move.invoice_user_id.id, + 'auto_post': True if reverse_date > fields.Date.context_today(self) else False, + } + + def _reverse_moves_post_hook(self, moves): + # DEPRECATED: TO REMOVE IN MASTER + return + + def reverse_moves(self): + self.ensure_one() + moves = self.move_ids + + # Create default values. + default_values_list = [] + for move in moves: + default_values_list.append(self._prepare_default_reversal(move)) + + batches = [ + [self.env['account.move'], [], True], # Moves to be cancelled by the reverses. + [self.env['account.move'], [], False], # Others. + ] + for move, default_vals in zip(moves, default_values_list): + is_auto_post = bool(default_vals.get('auto_post')) + is_cancel_needed = not is_auto_post and self.refund_method in ('cancel', 'modify') + batch_index = 0 if is_cancel_needed else 1 + batches[batch_index][0] |= move + batches[batch_index][1].append(default_vals) + + # Handle reverse method. + moves_to_redirect = self.env['account.move'] + for moves, default_values_list, is_cancel_needed in batches: + new_moves = moves._reverse_moves(default_values_list, cancel=is_cancel_needed) + + if self.refund_method == 'modify': + moves_vals_list = [] + for move in moves.with_context(include_business_fields=True): + moves_vals_list.append(move.copy_data({'date': self.date if self.date_mode == 'custom' else move.date})[0]) + new_moves = self.env['account.move'].create(moves_vals_list) + + moves_to_redirect |= new_moves + + self.new_move_ids = moves_to_redirect + + # Create action. + action = { + 'name': _('Reverse Moves'), + 'type': 'ir.actions.act_window', + 'res_model': 'account.move', + } + if len(moves_to_redirect) == 1: + action.update({ + 'view_mode': 'form', + 'res_id': moves_to_redirect.id, + }) + else: + action.update({ + 'view_mode': 'tree,form', + 'domain': [('id', 'in', moves_to_redirect.ids)], + }) + return action |
