forked from Mapan/odoo17e
266 lines
13 KiB
Python
266 lines
13 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
from dateutil.relativedelta import relativedelta
|
|
|
|
from odoo import api, fields, models, tools, _
|
|
from odoo.exceptions import UserError, ValidationError, RedirectWarning
|
|
|
|
|
|
class AccountJournal(models.Model):
|
|
_inherit = "account.journal"
|
|
|
|
def __get_bank_statements_available_sources(self):
|
|
rslt = super(AccountJournal, self).__get_bank_statements_available_sources()
|
|
rslt.append(("online_sync", _("Automated Bank Synchronization")))
|
|
return rslt
|
|
|
|
next_link_synchronization = fields.Datetime("Online Link Next synchronization", related='account_online_link_id.next_refresh')
|
|
expiring_synchronization_date = fields.Date(related='account_online_link_id.expiring_synchronization_date')
|
|
expiring_synchronization_due_day = fields.Integer(compute='_compute_expiring_synchronization_due_day')
|
|
account_online_account_id = fields.Many2one('account.online.account', copy=False, ondelete='set null')
|
|
account_online_link_id = fields.Many2one('account.online.link', related='account_online_account_id.account_online_link_id', readonly=True, store=True)
|
|
account_online_link_state = fields.Selection(related="account_online_link_id.state", readonly=True)
|
|
renewal_contact_email = fields.Char(
|
|
string='Connection Requests',
|
|
help='Comma separated list of email addresses to send consent renewal notifications 15, 3 and 1 days before expiry',
|
|
default=lambda self: self.env.user.email,
|
|
)
|
|
online_sync_fetching_status = fields.Selection(related="account_online_account_id.fetching_status", readonly=True)
|
|
|
|
def write(self, vals):
|
|
# When changing the bank_statement_source, unlink the connection if there is any
|
|
if 'bank_statements_source' in vals and vals.get('bank_statements_source') != 'online_sync':
|
|
for journal in self:
|
|
if journal.bank_statements_source == 'online_sync':
|
|
# unlink current connection
|
|
vals['account_online_account_id'] = False
|
|
journal.account_online_link_id.has_unlinked_accounts = True
|
|
return super().write(vals)
|
|
|
|
@api.depends('expiring_synchronization_date')
|
|
def _compute_expiring_synchronization_due_day(self):
|
|
for record in self:
|
|
if record.expiring_synchronization_date:
|
|
due_day_delta = record.expiring_synchronization_date - fields.Date.context_today(record)
|
|
record.expiring_synchronization_due_day = due_day_delta.days
|
|
else:
|
|
record.expiring_synchronization_due_day = 0
|
|
|
|
@api.constrains('account_online_account_id')
|
|
def _check_account_online_account_id(self):
|
|
for journal in self:
|
|
if len(journal.account_online_account_id.journal_ids) > 1:
|
|
raise ValidationError(_('You cannot have two journals associated with the same Online Account.'))
|
|
|
|
def _fetch_online_transactions(self):
|
|
for journal in self:
|
|
try:
|
|
journal.account_online_link_id._pop_connection_state_details(journal=journal)
|
|
journal.manual_sync()
|
|
# for cron jobs it is usually recommended committing after each iteration,
|
|
# so that a later error or job timeout doesn't discard previous work
|
|
self.env.cr.commit()
|
|
except (UserError, RedirectWarning):
|
|
# We need to rollback here otherwise the next iteration will still have the error when trying to commit
|
|
self.env.cr.rollback()
|
|
|
|
@api.model
|
|
def _cron_fetch_waiting_online_transactions(self):
|
|
""" This method is only called when the user fetch transactions asynchronously.
|
|
We only fetch transactions on synchronizations that are in "waiting" status.
|
|
Once the synchronization is done, the status is changed for "done".
|
|
We have to that to avoid having too much logic in the same cron function to do
|
|
2 different things. This cron should only be used for asynchronous fetchs.
|
|
"""
|
|
|
|
# 'limit_time_real_cron' and 'limit_time_real' default respectively to -1 and 120.
|
|
# Manual fallbacks applied for non-POSIX systems where this key is disabled (set to None).
|
|
limit_time = tools.config['limit_time_real_cron'] or -1
|
|
if limit_time <= 0:
|
|
limit_time = tools.config['limit_time_real'] or 120
|
|
journals = self.search([(
|
|
'account_online_account_id', '!=', False),
|
|
'|',
|
|
('online_sync_fetching_status', 'in', ('planned', 'waiting')),
|
|
'&',
|
|
('online_sync_fetching_status', '=', 'processing'),
|
|
('account_online_link_id.last_refresh', '<', fields.Datetime.now() - relativedelta(seconds=limit_time)),
|
|
])
|
|
journals.with_context(cron=True)._fetch_online_transactions()
|
|
|
|
@api.model
|
|
def _cron_fetch_online_transactions(self):
|
|
""" This method is called by the cron (by default twice a day) to fetch (for all journals)
|
|
the new transactions.
|
|
"""
|
|
journals = self.search([('account_online_account_id', '!=', False)])
|
|
journals.with_context(cron=True)._fetch_online_transactions()
|
|
|
|
@api.model
|
|
def _cron_send_reminder_email(self):
|
|
for journal in self.search([('account_online_account_id', '!=', False)]):
|
|
if journal.expiring_synchronization_due_day in {1, 3, 15}:
|
|
journal.action_send_reminder()
|
|
|
|
def manual_sync(self):
|
|
self.ensure_one()
|
|
if self.account_online_link_id:
|
|
account = self.account_online_account_id
|
|
return self.account_online_link_id._fetch_transactions(accounts=account)
|
|
|
|
def unlink(self):
|
|
"""
|
|
Override of the unlink method.
|
|
That's useful to unlink account.online.account too.
|
|
"""
|
|
if self.account_online_account_id:
|
|
self.account_online_account_id.unlink()
|
|
return super(AccountJournal, self).unlink()
|
|
|
|
def action_configure_bank_journal(self):
|
|
"""
|
|
Override the "action_configure_bank_journal" and change the flow for the
|
|
"Configure" button in dashboard.
|
|
"""
|
|
self.ensure_one()
|
|
return self.env['account.online.link'].action_new_synchronization()
|
|
|
|
def action_open_account_online_link(self):
|
|
self.ensure_one()
|
|
return {
|
|
'type': 'ir.actions.act_window',
|
|
'name': self.account_online_link_id.name,
|
|
'res_model': 'account.online.link',
|
|
'target': 'main',
|
|
'view_mode': 'form',
|
|
'views': [[False, 'form']],
|
|
'res_id': self.account_online_link_id.id,
|
|
}
|
|
|
|
def action_extend_consent(self):
|
|
"""
|
|
Extend the consent of the user by redirecting him to update his credentials
|
|
"""
|
|
self.ensure_one()
|
|
return self.account_online_link_id._open_iframe(
|
|
mode='updateCredentials',
|
|
include_param={
|
|
'account_online_identifier': self.account_online_account_id.online_identifier,
|
|
},
|
|
)
|
|
|
|
def action_reconnect_online_account(self):
|
|
self.ensure_one()
|
|
return self.account_online_link_id.action_reconnect_account()
|
|
|
|
def action_send_reminder(self):
|
|
self.ensure_one()
|
|
self._portal_ensure_token()
|
|
template = self.env.ref('account_online_synchronization.email_template_sync_reminder')
|
|
subtype = self.env.ref('account_online_synchronization.bank_sync_consent_renewal')
|
|
self.message_post_with_source(source_ref=template, subtype_id=subtype.id)
|
|
|
|
def action_open_missing_transaction_wizard(self):
|
|
""" This method allows to open the wizard to fetch the missing
|
|
transactions and the pending ones.
|
|
Depending on where the function is called, we'll receive
|
|
one journal or none of them.
|
|
If we receive more or less than one journal, we do not set
|
|
it on the wizard, the user should select it by himself.
|
|
|
|
:return: An action opening the wizard.
|
|
"""
|
|
journal_id = None
|
|
if len(self) == 1:
|
|
if not self.account_online_account_id or self.account_online_link_state != 'connected':
|
|
raise UserError(_("You can't find missing transactions for a journal that isn't connected."))
|
|
|
|
journal_id = self.id
|
|
|
|
wizard = self.env['account.missing.transaction.wizard'].create({'journal_id': journal_id})
|
|
return {
|
|
'name': _("Find Missing Transactions"),
|
|
'type': 'ir.actions.act_window',
|
|
'res_model': 'account.missing.transaction.wizard',
|
|
'res_id': wizard.id,
|
|
'views': [(False, 'form')],
|
|
'target': 'new',
|
|
}
|
|
|
|
def action_open_dashboard_asynchronous_action(self):
|
|
""" This method allows to open action asynchronously
|
|
during the fetching process.
|
|
When a user clicks on the Fetch Transactions button in
|
|
the dashboard, we fetch the transactions asynchronously
|
|
and save connection state details on the synchronization.
|
|
This action allows the user to open the action saved in
|
|
the connection state details.
|
|
"""
|
|
self.ensure_one()
|
|
|
|
if not self.account_online_account_id:
|
|
raise UserError(_("You can only execute this action for bank-synchronized journals."))
|
|
|
|
connection_state_details = self.account_online_link_id._pop_connection_state_details(journal=self)
|
|
if connection_state_details and connection_state_details.get('action'):
|
|
if connection_state_details.get('error_type') == 'redirect_warning':
|
|
self.env.cr.commit()
|
|
raise RedirectWarning(connection_state_details['error_message'], connection_state_details['action'], _('Report Issue'))
|
|
else:
|
|
return connection_state_details['action']
|
|
|
|
return {'type': 'ir.actions.client', 'tag': 'soft_reload'}
|
|
|
|
def _get_journal_dashboard_data_batched(self):
|
|
dashboard_data = super()._get_journal_dashboard_data_batched()
|
|
for journal in self.filtered('account_online_link_id'):
|
|
if journal.company_id.id not in self.env.companies.ids:
|
|
continue
|
|
connection_state_details = journal.account_online_link_id._get_connection_state_details(journal=journal)
|
|
if not connection_state_details and journal.account_online_account_id.fetching_status in ('waiting', 'processing'):
|
|
connection_state_details = {'status': 'fetching'}
|
|
dashboard_data[journal.id]['connection_state_details'] = connection_state_details
|
|
return dashboard_data
|
|
|
|
def get_related_connection_state_details(self):
|
|
""" This method allows JS widget to get the last connection state details
|
|
It's useful if the user wasn't on the dashboard when we send the message
|
|
by websocket that the asynchronous flow is finished.
|
|
In case we don't have a connection state details and if the fetching
|
|
status is set on "waiting" or "processing". We're returning that the sync
|
|
is currently fetching.
|
|
"""
|
|
self.ensure_one()
|
|
connection_state_details = self.account_online_link_id._get_connection_state_details(journal=self)
|
|
if not connection_state_details and self.account_online_account_id.fetching_status in ('waiting', 'processing'):
|
|
connection_state_details = {'status': 'fetching'}
|
|
return connection_state_details
|
|
|
|
def _consume_connection_state_details(self):
|
|
self.ensure_one()
|
|
if self.account_online_link_id and self.user_has_groups('account.group_account_manager'):
|
|
# In case we have a bank synchronization connected to the journal
|
|
# we want to remove the last connection state because it means that we
|
|
# have "mark as read" this state, and we don't want to display it again to
|
|
# the user.
|
|
self.account_online_link_id._pop_connection_state_details(journal=self)
|
|
|
|
def open_action(self):
|
|
# Extends 'account_accountant'
|
|
if not self._context.get('action_name') and self.type == 'bank' and self.bank_statements_source == 'online_sync':
|
|
self._consume_connection_state_details()
|
|
return self.env['account.bank.statement.line']._action_open_bank_reconciliation_widget(
|
|
default_context={'search_default_journal_id': self.id},
|
|
)
|
|
return super().open_action()
|
|
|
|
def action_open_reconcile(self):
|
|
# Extends 'account_accountant'
|
|
self._consume_connection_state_details()
|
|
return super().action_open_reconcile()
|
|
|
|
def action_open_bank_transactions(self):
|
|
# Extends 'account_accountant'
|
|
self._consume_connection_state_details()
|
|
return super().action_open_bank_transactions()
|