193 lines
9.4 KiB
Python
193 lines
9.4 KiB
Python
from odoo import models, fields, api, _
|
|
from odoo.exceptions import UserError
|
|
|
|
class BankInternalTransfer(models.Model):
|
|
_name = 'bank.internal.transfer'
|
|
_description = 'Bank Internal Transfer'
|
|
_order = 'date desc, id desc'
|
|
|
|
name = fields.Char(string='Reference', required=True, copy=False, readonly=True, default=lambda self: _('New'))
|
|
source_journal_id = fields.Many2one('account.journal', string='Source Bank Journal', domain="[('type', '=', 'bank')]", required=True)
|
|
destination_journal_id = fields.Many2one('account.journal', string='Destination Bank Journal', domain="[('type', '=', 'bank')]", required=True)
|
|
date = fields.Date(string='Date', required=True, default=fields.Date.context_today)
|
|
amount = fields.Monetary(string='Amount', required=True)
|
|
currency_id = fields.Many2one('res.currency', compute='_compute_currency_id', store=True)
|
|
company_id = fields.Many2one('res.company', required=True, default=lambda self: self.env.company)
|
|
memo = fields.Char(string='Memo')
|
|
payment_method_line_id = fields.Many2one('account.payment.method.line', string='Payment Method', domain="[('journal_id', '=', source_journal_id), ('payment_type', '=', 'outbound')]")
|
|
state = fields.Selection([
|
|
('draft', 'Draft'),
|
|
('posted', 'Posted'),
|
|
], string='Status', default='draft', required=True)
|
|
source_statement_line_id = fields.Many2one('account.bank.statement.line', string='Source Statement Line', readonly=True)
|
|
destination_statement_line_id = fields.Many2one('account.bank.statement.line', string='Destination Statement Line', readonly=True)
|
|
|
|
# Fields for report compatibility (account.report_payment_receipt)
|
|
ref = fields.Char(string='Report Reference', related='memo')
|
|
partner_id = fields.Many2one('res.partner', compute='_compute_dummy_fields')
|
|
partner_type = fields.Char(compute='_compute_dummy_fields')
|
|
payment_method_id = fields.Many2one('account.payment.method', compute='_compute_payment_method_id')
|
|
reconciled_invoice_ids = fields.Many2many('account.move', compute='_compute_dummy_fields')
|
|
reconciled_bill_ids = fields.Many2many('account.move', compute='_compute_dummy_fields')
|
|
journal_id = fields.Many2one('account.journal', compute='_compute_dummy_fields')
|
|
partner_bank_id = fields.Many2one('res.partner.bank', compute='_compute_dummy_fields')
|
|
payment_ids = fields.Many2many('bank.internal.transfer', compute='_compute_dummy_fields', string='Payments')
|
|
deduction_line_ids = fields.Many2many('account.move', compute='_compute_dummy_fields')
|
|
amount_total = fields.Monetary(related='amount')
|
|
final_payment_amount = fields.Monetary(related='amount')
|
|
|
|
def _compute_dummy_fields(self):
|
|
for rec in self:
|
|
# Mimic standard Odoo internal transfer behavior where the company is the partner
|
|
rec.partner_id = rec.company_id.partner_id
|
|
rec.partner_type = 'supplier'
|
|
rec.reconciled_invoice_ids = False
|
|
rec.reconciled_bill_ids = False
|
|
rec.journal_id = rec.source_journal_id
|
|
rec.partner_bank_id = rec.destination_journal_id.bank_account_id
|
|
rec.payment_ids = rec.ids
|
|
rec.deduction_line_ids = False
|
|
|
|
@api.depends('payment_method_line_id')
|
|
def _compute_payment_method_id(self):
|
|
for rec in self:
|
|
rec.payment_method_id = rec.payment_method_line_id.payment_method_id
|
|
|
|
def _get_payment_receipt_report_values(self):
|
|
self.ensure_one()
|
|
return {
|
|
'display_payment_method': True,
|
|
'display_invoices': False,
|
|
}
|
|
|
|
@api.depends('source_journal_id')
|
|
def _compute_currency_id(self):
|
|
for record in self:
|
|
record.currency_id = record.source_journal_id.currency_id or record.company_id.currency_id
|
|
|
|
@api.model_create_multi
|
|
def create(self, vals_list):
|
|
for vals in vals_list:
|
|
if vals.get('name', _('New')) == _('New'):
|
|
vals['name'] = self.env['ir.sequence'].next_by_code('bank.internal.transfer') or _('New')
|
|
return super().create(vals_list)
|
|
|
|
def action_confirm(self):
|
|
for transfer in self:
|
|
if transfer.amount <= 0:
|
|
raise UserError(_("Amount must be strictly positive."))
|
|
if transfer.source_journal_id == transfer.destination_journal_id:
|
|
raise UserError(_("Source and destination journals must be different."))
|
|
|
|
# 1. Determine Outstanding Payments account from Source Journal
|
|
outstanding_account = transfer.source_journal_id.outbound_payment_method_line_ids.mapped('payment_account_id')
|
|
if outstanding_account:
|
|
outstanding_account = outstanding_account[0]
|
|
else:
|
|
outstanding_account = transfer.company_id.account_journal_payment_credit_account_id
|
|
|
|
if not outstanding_account:
|
|
raise UserError(_("Please configure the Outstanding Payments Account on the source journal or company."))
|
|
|
|
# 2. Create the Bank Statement Line for the Source Journal
|
|
stmt_line_vals = {
|
|
'journal_id': transfer.source_journal_id.id,
|
|
'date': transfer.date,
|
|
'payment_ref': transfer.memo or transfer.name,
|
|
'amount': -transfer.amount, # Outgoing money
|
|
'bank_internal_transfer_id': transfer.id,
|
|
}
|
|
stmt_line = self.env['account.bank.statement.line'].create(stmt_line_vals)
|
|
|
|
# 3. Modify the underlying move's suspense line to use the Outstanding Payments account
|
|
# This effectively "reconciles" the source line against the transfer
|
|
suspense_account = transfer.source_journal_id.suspense_account_id
|
|
if not suspense_account:
|
|
raise UserError(_("Please configure the Suspense Account on the source journal."))
|
|
|
|
move = stmt_line.move_id
|
|
suspense_line = move.line_ids.filtered(lambda l: l.account_id == suspense_account)
|
|
if not suspense_line:
|
|
suspense_line = move.line_ids.filtered(lambda l: l.debit > 0)
|
|
|
|
if suspense_line:
|
|
suspense_line.with_context(check_move_validity=False).write({
|
|
'account_id': outstanding_account.id,
|
|
})
|
|
|
|
# Post the move
|
|
move.action_post()
|
|
|
|
# Link and update state
|
|
transfer.write({
|
|
'source_statement_line_id': stmt_line.id,
|
|
'state': 'posted',
|
|
})
|
|
|
|
def _automate_destination_leg(self):
|
|
""" Automatically create and reconcile the destination bank statement line.
|
|
This is called when the source statement line is 'Reviewed'.
|
|
"""
|
|
self.ensure_one()
|
|
if self.destination_statement_line_id:
|
|
return
|
|
|
|
# 1. Determine the transit account
|
|
transit_account = self.source_journal_id.outbound_payment_method_line_ids.mapped('payment_account_id')
|
|
if transit_account:
|
|
transit_account = transit_account[0]
|
|
else:
|
|
# Fallback to default transit account code 218401
|
|
transit_account = self.env['account.account'].search([
|
|
('code', '=', '218401'),
|
|
('company_id', '=', self.company_id.id)
|
|
], limit=1)
|
|
|
|
if not transit_account:
|
|
transit_account = self.company_id.account_journal_payment_credit_account_id
|
|
|
|
if not transit_account:
|
|
raise UserError(_("Could not find a suitable transit account (218401 or Outstanding Payments)."))
|
|
|
|
# 2. Create the Bank Statement Line for the Destination Journal
|
|
dst_st_line_vals = {
|
|
'journal_id': self.destination_journal_id.id,
|
|
'date': self.date,
|
|
'payment_ref': self.memo or self.name,
|
|
'amount': self.amount, # Incoming money
|
|
'bank_internal_transfer_id': self.id,
|
|
}
|
|
dst_st_line = self.env['account.bank.statement.line'].create(dst_st_line_vals)
|
|
self.destination_statement_line_id = dst_st_line
|
|
|
|
# 3. Reconcile the destination line against the transit account
|
|
move = dst_st_line.move_id
|
|
suspense_account = self.destination_journal_id.suspense_account_id
|
|
suspense_line = move.line_ids.filtered(lambda l: l.account_id == suspense_account)
|
|
if not suspense_line:
|
|
suspense_line = move.line_ids.filtered(lambda l: l.credit > 0)
|
|
|
|
if suspense_line:
|
|
suspense_line.with_context(check_move_validity=False).write({
|
|
'account_id': transit_account.id,
|
|
})
|
|
|
|
# 4. Mark as reviewed automatically
|
|
move.set_moves_checked(True)
|
|
|
|
def action_draft(self):
|
|
for transfer in self:
|
|
if transfer.source_statement_line_id:
|
|
if transfer.source_statement_line_id.is_reconciled and transfer.destination_statement_line_id:
|
|
raise UserError(_("You cannot reset a transfer that has already been processed on the destination side."))
|
|
|
|
transfer.source_statement_line_id.move_id.button_draft()
|
|
transfer.source_statement_line_id.unlink()
|
|
|
|
if transfer.destination_statement_line_id:
|
|
transfer.destination_statement_line_id.move_id.button_draft()
|
|
transfer.destination_statement_line_id.unlink()
|
|
|
|
transfer.state = 'draft'
|
|
|