# Fusion Accounting - Account Transfer Wizard # Copyright (C) 2026 Nexa Systems Inc. (https://nexasystems.ca) # Original implementation for the Fusion Accounting module. # # Provides a transient model that creates a journal entry to move # a balance from one account to another within the same company. import logging from datetime import date from odoo import api, fields, models, Command, _ from odoo.exceptions import UserError, ValidationError _logger = logging.getLogger(__name__) class FusionAccountTransfer(models.TransientModel): """Wizard for transferring balances between two accounts. Creates a balanced journal entry with one debit line and one credit line, effectively moving a specified amount from the source account to the destination account. """ _name = 'fusion.account.transfer' _description = 'Account Balance Transfer' # ===================================================================== # Fields # ===================================================================== company_id = fields.Many2one( comodel_name='res.company', string="Company", required=True, default=lambda self: self.env.company, readonly=True, ) currency_id = fields.Many2one( comodel_name='res.currency', string="Currency", related='company_id.currency_id', readonly=True, ) source_account_id = fields.Many2one( comodel_name='account.account', string="Source Account", required=True, domain="[('company_ids', 'in', company_id)]", help="The account to transfer funds FROM (will be credited).", ) destination_account_id = fields.Many2one( comodel_name='account.account', string="Destination Account", required=True, domain="[('company_ids', 'in', company_id)]", help="The account to transfer funds TO (will be debited).", ) amount = fields.Monetary( string="Amount", required=True, currency_field='currency_id', help="Amount to transfer between the accounts.", ) journal_id = fields.Many2one( comodel_name='account.journal', string="Journal", required=True, domain="[('type', '=', 'general'), ('company_id', '=', company_id)]", help="Miscellaneous journal to record the transfer entry.", ) date = fields.Date( string="Date", required=True, default=fields.Date.context_today, help="Date of the transfer journal entry.", ) memo = fields.Char( string="Memo", help="Optional description for the journal entry.", ) partner_id = fields.Many2one( comodel_name='res.partner', string="Partner", help="Optional partner for the journal entry lines.", ) # ===================================================================== # Constraints # ===================================================================== @api.constrains('source_account_id', 'destination_account_id') def _check_different_accounts(self): for record in self: if record.source_account_id == record.destination_account_id: raise ValidationError( _("Source and destination accounts must be different.") ) @api.constrains('amount') def _check_positive_amount(self): for record in self: if record.amount <= 0: raise ValidationError( _("Transfer amount must be greater than zero.") ) # ===================================================================== # Default Values # ===================================================================== @api.model def default_get(self, fields_list): """Set default journal to the company's miscellaneous journal.""" defaults = super().default_get(fields_list) if 'journal_id' not in defaults: misc_journal = self.env['account.journal'].search([ ('type', '=', 'general'), ('company_id', '=', self.env.company.id), ], limit=1) if misc_journal: defaults['journal_id'] = misc_journal.id return defaults # ===================================================================== # Action # ===================================================================== def action_transfer(self): """Create a journal entry moving balance between accounts. Generates a balanced journal entry: - Credit line on the source account - Debit line on the destination account :returns: action dict pointing to the created journal entry :raises UserError: if required fields are missing or invalid """ self.ensure_one() if not self.source_account_id or not self.destination_account_id: raise UserError(_("Both source and destination accounts are required.")) if self.amount <= 0: raise UserError(_("The transfer amount must be positive.")) ref = self.memo or _("Account Transfer: %s → %s", self.source_account_id.display_name, self.destination_account_id.display_name) move_vals = { 'journal_id': self.journal_id.id, 'date': self.date, 'ref': ref, 'company_id': self.company_id.id, 'move_type': 'entry', 'line_ids': [ # Credit the source account Command.create({ 'account_id': self.source_account_id.id, 'name': ref, 'debit': 0.0, 'credit': self.amount, 'partner_id': self.partner_id.id if self.partner_id else False, }), # Debit the destination account Command.create({ 'account_id': self.destination_account_id.id, 'name': ref, 'debit': self.amount, 'credit': 0.0, 'partner_id': self.partner_id.id if self.partner_id else False, }), ], } move = self.env['account.move'].create(move_vals) _logger.info( "Fusion Account Transfer: created journal entry %s (id=%s) " "for %.2f from %s to %s", move.name, move.id, self.amount, self.source_account_id.code, self.destination_account_id.code, ) return { 'type': 'ir.actions.act_window', 'res_model': 'account.move', 'res_id': move.id, 'view_mode': 'form', 'target': 'current', 'name': _("Transfer Entry"), }