# Copyright 2017-2019 Akretion France (http://www.akretion.com/) # @author: Alexis de Lattre # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from odoo import _, fields, models class BankReconciliationXlsx(models.AbstractModel): _name = 'report.bank.reconciliation.xlsx' _inherit = 'report.report_xlsx.abstract' def _compute_account_balance(self, journal, date): bank_account = journal.default_debit_account_id amount_field = 'balance' # TODO: add support for bank accounts in foreign currency # if not o.currency_id else 'amount_currency' query = """ SELECT sum(%s) FROM account_move_line WHERE account_id=%%s AND date <= %%s""" % (amount_field, ) self.env.cr.execute(query, (bank_account.id, date)) query_results = self.env.cr.dictfetchall() if query_results: account_bal = query_results[0].get('sum') or 0.0 else: account_bal = 0.0 return account_bal def _prepare_move_lines(self, journal, date): bank_account = journal.default_debit_account_id mlines = self.env['account.move.line'].search([ ('account_id', '=', bank_account.id), ('journal_id', '=', journal.id), # to avoid initial line ('date', '<=', date), '|', ('statement_line_date', '=', False), ('statement_line_date', '>', date)]) res = [] for mline in mlines: move = mline.move_id cpart = [] for line in move.line_ids: if ( line.account_id != bank_account and line.account_id.code not in cpart): cpart.append(line.account_id.code) counterpart = ' ,'.join(cpart) res.append({ 'date': mline.date, 'label': mline.name, 'ref': mline.ref or '', 'partner': mline.partner_id.display_name or '', 'amount': mline.balance, 'statement_line_date': mline.statement_line_date or '', 'move_number': move.name, 'counterpart': counterpart, }) return res def _prepare_draft_statement_lines(self, journal, date): blines = self.env['account.bank.statement.line'].search([ ('journal_entry_ids', '=', False), ('journal_id', '=', journal.id), ('date', '<=', date)]) res = [] for bline in blines: res.append({ 'date': bline.date, 'label': bline.name, 'ref': bline.ref or '', 'partner': bline.partner_id.display_name or '', 'amount': bline.amount, 'statement_ref': bline.statement_id.display_name, }) return res def generate_xlsx_report(self, workbook, data, wizard): date = wizard.date date_dt = fields.Date.from_string(date) no_bank_journal = True for o in wizard.journal_ids: no_bank_journal = False # Start styles lang_code = self.env.user.lang lang = False if lang_code: lang = self.env['res.lang'].search([('code', '=', lang_code)]) if not lang: lang = self.env['res.lang'].search([], limit=1) xls_date_format = lang.date_format.replace('%Y', 'yyyy').\ replace('%m', 'mm').replace('%d', 'dd').replace('%y', 'yy') doc_title = workbook.add_format({'bold': True, 'font_size': 16}) col_title = workbook.add_format({ 'bold': True, 'bg_color': '#e2e2fa', 'text_wrap': True, 'font_size': 10, }) title_right = workbook.add_format({ 'bold': True, 'bg_color': '#e6e6fa', 'font_size': 10, 'align': 'right', }) title_date = workbook.add_format({ 'bg_color': '#f6f6ff', 'bold': True, 'num_format': xls_date_format, 'font_size': 10, 'align': 'left'}) label_bold = workbook.add_format({ 'bold': True, 'text_wrap': False, 'font_size': 10}) none = workbook.add_format({ 'bold': True, 'font_size': 10, 'align': 'right'}) regular = workbook.add_format({'font_size': 10}) if '%' in xls_date_format: # fallback xls_date_format = 'yyyy-mm-dd' regular_date = workbook.add_format({ 'num_format': xls_date_format, 'font_size': 10, 'align': 'left'}) cur_format = u'#,##0.00 %s' % ( o.company_id.currency_id.symbol or o.company_id.currency_id.name) # It seems that Excel replaces automatically the decimal # and thousand separator by those of the language under which # Excel runs regular_currency = workbook.add_format( {'num_format': cur_format, 'font_size': 10}) regular_currency_bg = workbook.add_format({ 'num_format': cur_format, 'font_size': 10, 'bg_color': '#f6f6ff'}) # End styles sheet = workbook.add_worksheet(o.code or o.name) sheet.write( 0, 0, _('%s - %s - Bank Reconciliation') % ( o.company_id.name, o.display_name), doc_title) sheet.set_row(0, 26) sheet.set_row(1, 25) sheet.set_column(0, 0, 10) sheet.set_column(1, 1, 40) sheet.set_column(2, 2, 15) sheet.set_column(3, 3, 25) sheet.set_column(4, 4, 12) sheet.set_column(5, 5, 18) sheet.set_column(6, 6, 14) sheet.set_column(7, 7, 14) row = 2 sheet.write(row, 0, _("Date:"), title_right) sheet.write(row, 1, date_dt, title_date) # 1) Show accounting balance of bank account row += 2 bank_account = o.default_debit_account_id for col in range(3): sheet.write(row, col, '', title_right) sheet.write( row, 3, _('Balance %s:') % bank_account.code, title_right) account_bal = self._compute_account_balance(o, date) sheet.write(row, 4, account_bal, regular_currency_bg) bank_bal = account_bal formula = '=E%d' % (row + 1) # 2) Show account move line that are not linked to bank statement # line or linked to a statement line after the date row += 2 sheet.write( row, 0, _( 'Journal items of account %s not linked to a bank ' 'statement line:') % bank_account.code, label_bold) mlines = self._prepare_move_lines(o, date) if not mlines: sheet.write(row, 4, _('NONE'), none) else: row += 1 col_labels = [ _('Date'), _('Label'), _('Ref.'), _('Partner'), _('Amount'), _('Statement Line Date'), _('Move Number'), _('Counter-part')] col = 0 for col_label in col_labels: sheet.write(row, col, col_label, col_title) col += 1 m_start_row = m_end_row = row + 1 for mline in mlines: row += 1 m_end_row = row bank_bal -= mline['amount'] sheet.write(row, 0, mline['date'], regular_date) sheet.write(row, 1, mline['label'], regular) sheet.write(row, 2, mline['ref'], regular) sheet.write(row, 3, mline['partner'], regular) sheet.write(row, 4, mline['amount'], regular_currency) sheet.write( row, 5, mline['statement_line_date'], regular_date) sheet.write(row, 6, mline['move_number'], regular) sheet.write(row, 7, mline['counterpart'], regular) formula += '-SUM(E%d:E%d)' % (m_start_row + 1, m_end_row + 1) # 3) Add draft bank statement lines row += 2 # skip 1 line sheet.write( row, 0, _( 'Draft bank statement lines:'), label_bold) blines = self._prepare_draft_statement_lines(o, date) if not blines: sheet.write(row, 4, _('NONE'), none) else: row += 1 col_labels = [ _('Date'), _('Label'), _('Ref.'), _('Partner'), _('Amount'), _('Statement Ref.'), '', ''] col = 0 for col_label in col_labels: sheet.write(row, col, col_label, col_title) col += 1 b_start_row = b_end_row = row + 1 for bline in blines: row += 1 b_end_row = row bank_bal += bline['amount'] sheet.write(row, 0, bline['date'], regular_date) sheet.write(row, 1, bline['label'], regular) sheet.write(row, 2, bline['ref'], regular) sheet.write(row, 3, bline['partner'], regular) sheet.write(row, 4, bline['amount'], regular_currency) sheet.write( row, 5, bline['statement_ref'], regular_currency) formula += '+SUM(E%d:E%d)' % (b_start_row + 1, b_end_row + 1) # 4) Theoric bank account balance at the bank row += 2 for col in range(3): sheet.write(row, col, '', title_right) sheet.write( row, 3, _('Computed Bank Account Balance at the Bank:'), title_right) sheet.write_formula( row, 4, formula, regular_currency_bg, bank_bal) if no_bank_journal: sheet = workbook.add_worksheet(_('No Bank Journal')) sheet.set_row(0, 30) warn_msg = workbook.add_format( {'bold': True, 'font_size': 16, 'font_color': '#003b6f'}) sheet.write( 0, 0, _( "No bank journal selected. " "This report is only for bank journals."), warn_msg)