1
0
forked from Mapan/odoo17e
odoo17e-kedaikipas58/addons/account_online_synchronization/models/account_journal.py
2024-12-10 09:04:09 +07:00

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()