From 6f00485dc4826aa8ec073fd429cccb6aabbef42c Mon Sep 17 00:00:00 2001 From: jcoux Date: Fri, 29 Jul 2016 09:22:59 +0200 Subject: [PATCH] Add OCA Aged Partner Balance XLSX --- account_financial_report_qweb/README.rst | 4 - .../report/__init__.py | 1 + .../report/abstract_report_xlsx.py | 11 + .../report/aged_partner_balance.py | 10 +- .../report/aged_partner_balance_xlsx.py | 269 ++++++++++++++++++ .../report/trial_balance_xlsx.py | 2 +- account_financial_report_qweb/reports.xml | 9 + .../tests/test_aged_partner_balance.py | 27 ++ .../wizard/aged_partner_balance_wizard.py | 9 +- .../aged_partner_balance_wizard_view.xml | 2 + 10 files changed, 334 insertions(+), 10 deletions(-) create mode 100644 account_financial_report_qweb/report/aged_partner_balance_xlsx.py diff --git a/account_financial_report_qweb/README.rst b/account_financial_report_qweb/README.rst index ed3c89e6..307f02d9 100644 --- a/account_financial_report_qweb/README.rst +++ b/account_financial_report_qweb/README.rst @@ -21,10 +21,6 @@ Accunting / Reporting / OCA Reports. Known issues / Roadmap ====================== -Some reports are being worked on and will be available at some point: - -- Aged Partner Balance (XLSX) - Bug Tracker =========== diff --git a/account_financial_report_qweb/report/__init__.py b/account_financial_report_qweb/report/__init__.py index abb2e865..111bf4f4 100644 --- a/account_financial_report_qweb/report/__init__.py +++ b/account_financial_report_qweb/report/__init__.py @@ -6,6 +6,7 @@ from . import abstract_report_xlsx from . import aged_partner_balance +from . import aged_partner_balance_xlsx from . import general_ledger from . import general_ledger_xlsx from . import open_items diff --git a/account_financial_report_qweb/report/abstract_report_xlsx.py b/account_financial_report_qweb/report/abstract_report_xlsx.py index b5481579..5debe764 100644 --- a/account_financial_report_qweb/report/abstract_report_xlsx.py +++ b/account_financial_report_qweb/report/abstract_report_xlsx.py @@ -24,12 +24,14 @@ class AbstractReportXslx(ReportXlsx): # Formats self.format_right = None + self.format_right_bold_italic = None self.format_bold = None self.format_header_left = None self.format_header_center = None self.format_header_right = None self.format_header_amount = None self.format_amount = None + self.format_percent_bold_italic = None def generate_xlsx_report(self, workbook, data, objects): report = objects @@ -59,14 +61,19 @@ class AbstractReportXslx(ReportXlsx): Available formats are : * format_bold * format_right + * format_right_bold_italic * format_header_left * format_header_center * format_header_right * format_header_amount * format_amount + * format_percent_bold_italic """ self.format_bold = workbook.add_format({'bold': True}) self.format_right = workbook.add_format({'align': 'right'}) + self.format_right_bold_italic = workbook.add_format( + {'align': 'right', 'bold': True, 'italic': True} + ) self.format_header_left = workbook.add_format( {'bold': True, 'border': True, @@ -88,6 +95,10 @@ class AbstractReportXslx(ReportXlsx): self.format_header_amount.set_num_format('#,##0.00') self.format_amount = workbook.add_format() self.format_amount.set_num_format('#,##0.00') + self.format_percent_bold_italic = workbook.add_format( + {'bold': True, 'italic': True} + ) + self.format_percent_bold_italic.set_num_format('#,##0.00%') def _set_column_width(self): """Set width for all defined columns. diff --git a/account_financial_report_qweb/report/aged_partner_balance.py b/account_financial_report_qweb/report/aged_partner_balance.py index a34a12df..3fe45ce4 100644 --- a/account_financial_report_qweb/report/aged_partner_balance.py +++ b/account_financial_report_qweb/report/aged_partner_balance.py @@ -185,11 +185,15 @@ class AgedPartnerBalanceReportCompute(models.TransientModel): _inherit = 'report_aged_partner_balance_qweb' @api.multi - def print_report(self): + def print_report(self, xlsx_report=False): self.ensure_one() self.compute_data_for_report() - report_name = 'account_financial_report_qweb.' \ - 'report_aged_partner_balance_qweb' + if xlsx_report: + report_name = 'account_financial_report_qweb.' \ + 'report_aged_partner_balance_xlsx' + else: + report_name = 'account_financial_report_qweb.' \ + 'report_aged_partner_balance_qweb' return self.env['report'].get_action(records=self, report_name=report_name) diff --git a/account_financial_report_qweb/report/aged_partner_balance_xlsx.py b/account_financial_report_qweb/report/aged_partner_balance_xlsx.py new file mode 100644 index 00000000..7f31ea4b --- /dev/null +++ b/account_financial_report_qweb/report/aged_partner_balance_xlsx.py @@ -0,0 +1,269 @@ +# -*- coding: utf-8 -*- +# Author: Julien Coux +# Copyright 2016 Camptocamp SA +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import abstract_report_xlsx +from openerp.report import report_sxw +from openerp import _ + + +class AgedPartnerBalanceXslx(abstract_report_xlsx.AbstractReportXslx): + + def __init__(self, name, table, rml=False, parser=False, header=True, + store=False): + super(AgedPartnerBalanceXslx, self).__init__( + name, table, rml, parser, header, store) + + def _get_report_name(self): + return _('Aged Partner Balance') + + def _get_report_columns(self, report): + if not report.show_move_line_details: + return { + 0: {'header': _('Partner'), 'field': 'partner', 'width': 70}, + 1: {'header': _('Residual'), + 'field': 'amount_residual', + 'field_footer_total': 'cumul_amount_residual', + 'type': 'amount', + 'width': 14}, + 2: {'header': _('Current'), + 'field': 'current', + 'field_footer_total': 'cumul_current', + 'field_footer_percent': 'percent_current', + 'type': 'amount', + 'width': 14}, + 3: {'header': _(u'Age ≤ 30 d.'), + 'field': 'age_30_days', + 'field_footer_total': 'cumul_age_30_days', + 'field_footer_percent': 'percent_age_30_days', + 'type': 'amount', + 'width': 14}, + 4: {'header': _(u'Age ≤ 60 d.'), + 'field': 'age_60_days', + 'field_footer_total': 'cumul_age_60_days', + 'field_footer_percent': 'percent_age_60_days', + 'type': 'amount', + 'width': 14}, + 5: {'header': _(u'Age ≤ 90 d.'), + 'field': 'age_90_days', + 'field_footer_total': 'cumul_age_90_days', + 'field_footer_percent': 'percent_age_90_days', + 'type': 'amount', + 'width': 14}, + 6: {'header': _(u'Age ≤ 120 d.'), + 'field': 'age_120_days', + 'field_footer_total': 'cumul_age_120_days', + 'field_footer_percent': 'percent_age_120_days', + 'type': 'amount', + 'width': 14}, + 7: {'header': _('Older'), + 'field': 'older', + 'field_footer_total': 'cumul_older', + 'field_footer_percent': 'percent_older', + 'type': 'amount', + 'width': 14}, + } + else: + return { + 0: {'header': _('Date'), 'field': 'date', 'width': 11}, + 1: {'header': _('Entry'), 'field': 'entry', 'width': 18}, + 2: {'header': _('Journal'), 'field': 'journal', 'width': 8}, + 3: {'header': _('Account'), 'field': 'account', 'width': 9}, + 4: {'header': _('Partner'), 'field': 'partner', 'width': 25}, + 5: {'header': _('Ref - Label'), 'field': 'label', 'width': 40}, + 6: {'header': _('Due date'), 'field': 'date_due', 'width': 11}, + 7: {'header': _('Residual'), + 'field': 'amount_residual', + 'field_footer_total': 'cumul_amount_residual', + 'field_final_balance': 'amount_residual', + 'type': 'amount', + 'width': 14}, + 8: {'header': _('Current'), + 'field': 'current', + 'field_footer_total': 'cumul_current', + 'field_footer_percent': 'percent_current', + 'field_final_balance': 'current', + 'type': 'amount', + 'width': 14}, + 9: {'header': _(u'Age ≤ 30 d.'), + 'field': 'age_30_days', + 'field_footer_total': 'cumul_age_30_days', + 'field_footer_percent': 'percent_age_30_days', + 'field_final_balance': 'age_30_days', + 'type': 'amount', + 'width': 14}, + 10: {'header': _(u'Age ≤ 60 d.'), + 'field': 'age_60_days', + 'field_footer_total': 'cumul_age_60_days', + 'field_footer_percent': 'percent_age_60_days', + 'field_final_balance': 'age_60_days', + 'type': 'amount', + 'width': 14}, + 11: {'header': _(u'Age ≤ 90 d.'), + 'field': 'age_90_days', + 'field_footer_total': 'cumul_age_90_days', + 'field_footer_percent': 'percent_age_90_days', + 'field_final_balance': 'age_90_days', + 'type': 'amount', + 'width': 14}, + 12: {'header': _(u'Age ≤ 120 d.'), + 'field': 'age_120_days', + 'field_footer_total': 'cumul_age_120_days', + 'field_footer_percent': 'percent_age_120_days', + 'field_final_balance': 'age_120_days', + 'type': 'amount', + 'width': 14}, + 13: {'header': _('Older'), + 'field': 'older', + 'field_footer_total': 'cumul_older', + 'field_footer_percent': 'percent_older', + 'field_final_balance': 'older', + 'type': 'amount', + 'width': 14}, + } + + def _get_report_filters(self, report): + return [ + [_('Date at filter'), report.date_at], + [_('Target moves filter'), + _('All posted entries') if report.only_posted_moves + else _('All entries')], + ] + + def _get_col_count_filter_name(self): + return 2 + + def _get_col_count_filter_value(self): + return 3 + + def _get_col_pos_footer_label(self, report): + return 0 if not report.show_move_line_details else 5 + + def _get_col_count_final_balance_name(self): + return 5 + + def _get_col_pos_final_balance_label(self): + return 5 + + def _generate_report_content(self, workbook, report): + if not report.show_move_line_details: + # For each account + for account in report.account_ids: + # Write account title + self.write_array_title(account.code + ' - ' + account.name) + + # Display array header for partners lines + self.write_array_header() + + # Display partner lines + for partner in account.partner_ids: + self.write_line(partner.line_ids) + + # Display account lines + self.write_account_footer(report, + account, + _('Total'), + 'field_footer_total', + self.format_header_right, + self.format_header_amount, + False) + self.write_account_footer(report, + account, + _('Percents'), + 'field_footer_percent', + self.format_right_bold_italic, + self.format_percent_bold_italic, + True) + + # 2 lines break + self.row_pos += 2 + else: + # For each account + for account in report.account_ids: + # Write account title + self.write_array_title(account.code + ' - ' + account.name) + + # For each partner + for partner in account.partner_ids: + # Write partner title + self.write_array_title(partner.name) + + # Display array header for move lines + self.write_array_header() + + # Display account move lines + for line in partner.move_line_ids: + self.write_line(line) + + # Display ending balance line for partner + self.write_ending_balance(partner.line_ids) + + # Line break + self.row_pos += 1 + + # Display account lines + self.write_account_footer(report, + account, + _('Total'), + 'field_footer_total', + self.format_header_right, + self.format_header_amount, + False) + self.write_account_footer(report, + account, + _('Percents'), + 'field_footer_percent', + self.format_right_bold_italic, + self.format_percent_bold_italic, + True) + + # 2 lines break + self.row_pos += 2 + + def write_ending_balance(self, my_object): + """ + Specific function to write ending partner balance + for Aged Partner Balance + """ + name = None + label = _('Partner cumul aged balance') + super(AgedPartnerBalanceXslx, self).write_ending_balance( + my_object, name, label + ) + + def write_account_footer(self, report, account, label, field_name, + string_format, amount_format, amount_is_percent): + """ + Specific function to write account footer for Aged Partner Balance + """ + col_pos_footer_label = self._get_col_pos_footer_label(report) + for col_pos, column in self.columns.iteritems(): + if col_pos == col_pos_footer_label or column.get(field_name): + if col_pos == col_pos_footer_label: + value = label + else: + value = getattr(account, column[field_name]) + cell_type = column.get('type', 'string') + if cell_type == 'string' or col_pos == col_pos_footer_label: + self.sheet.write_string(self.row_pos, col_pos, value or '', + string_format) + elif cell_type == 'amount': + number = float(value) + if amount_is_percent: + number /= 100 + self.sheet.write_number(self.row_pos, col_pos, + number, + amount_format) + else: + self.sheet.write_string(self.row_pos, col_pos, '', + string_format) + + self.row_pos += 1 + + +AgedPartnerBalanceXslx( + 'report.account_financial_report_qweb.report_aged_partner_balance_xlsx', + 'report_aged_partner_balance_qweb', + parser=report_sxw.rml_parse +) diff --git a/account_financial_report_qweb/report/trial_balance_xlsx.py b/account_financial_report_qweb/report/trial_balance_xlsx.py index fb21129f..a56b3b4f 100644 --- a/account_financial_report_qweb/report/trial_balance_xlsx.py +++ b/account_financial_report_qweb/report/trial_balance_xlsx.py @@ -102,7 +102,7 @@ class TrialBalanceXslx(abstract_report_xlsx.AbstractReportXslx): # Display partner lines self.write_line(partner) - # Display account lines + # Display account footer line self.write_account_footer(account, account.code + ' - ' + account.name) diff --git a/account_financial_report_qweb/reports.xml b/account_financial_report_qweb/reports.xml index ffa2e646..33330c0c 100644 --- a/account_financial_report_qweb/reports.xml +++ b/account_financial_report_qweb/reports.xml @@ -102,4 +102,13 @@ + + Aged Partner Balance XLSX + report_aged_partner_balance_qweb + ir.actions.report.xml + account_financial_report_qweb.report_aged_partner_balance_xlsx + xlsx + + + diff --git a/account_financial_report_qweb/tests/test_aged_partner_balance.py b/account_financial_report_qweb/tests/test_aged_partner_balance.py index fe2456c2..9920af73 100644 --- a/account_financial_report_qweb/tests/test_aged_partner_balance.py +++ b/account_financial_report_qweb/tests/test_aged_partner_balance.py @@ -45,3 +45,30 @@ class TestAgedPartnerBalance(TransactionCase): report_html = self.env['report'].get_html(self.report, report_name) self.assertRegexpMatches(report_html, 'Aged Partner Balance') self.assertRegexpMatches(report_html, self.report.account_ids[0].name) + + def test_03_generation_report_xlsx(self): + """Check if report XLSX is correctly generated""" + + report_name = 'account_financial_report_qweb.' \ + 'report_aged_partner_balance_xlsx' + # Check if returned report action is correct + report_action = self.report.print_report(xlsx_report=True) + self.assertDictContainsSubset( + { + 'type': 'ir.actions.report.xml', + 'report_name': report_name, + 'report_type': 'xlsx', + }, + report_action + ) + + # Check if report template is correct + action_name = 'account_financial_report_qweb.' \ + 'action_report_aged_partner_balance_xlsx' + report_xlsx = self.env.ref(action_name).render_report( + self.report.ids, + report_name, + {'report_type': u'xlsx'} + ) + self.assertGreaterEqual(len(report_xlsx[0]), 1) + self.assertEqual(report_xlsx[1], 'xlsx') diff --git a/account_financial_report_qweb/wizard/aged_partner_balance_wizard.py b/account_financial_report_qweb/wizard/aged_partner_balance_wizard.py index e591881c..8bc9c121 100644 --- a/account_financial_report_qweb/wizard/aged_partner_balance_wizard.py +++ b/account_financial_report_qweb/wizard/aged_partner_balance_wizard.py @@ -57,7 +57,12 @@ class AgedPartnerBalance(models.TransientModel): self.ensure_one() return self._export() - def _export(self): + @api.multi + def button_export_xlsx(self): + self.ensure_one() + return self._export(xlsx_report=True) + + def _export(self, xlsx_report=False): """Default export is PDF.""" model = self.env['report_aged_partner_balance_qweb'] report = model.create({ @@ -68,4 +73,4 @@ class AgedPartnerBalance(models.TransientModel): 'filter_partner_ids': [(6, 0, self.partner_ids.ids)], 'show_move_line_details': self.show_move_line_details, }) - return report.print_report() + return report.print_report(xlsx_report) diff --git a/account_financial_report_qweb/wizard/aged_partner_balance_wizard_view.xml b/account_financial_report_qweb/wizard/aged_partner_balance_wizard_view.xml index ca49f9f4..be180406 100644 --- a/account_financial_report_qweb/wizard/aged_partner_balance_wizard_view.xml +++ b/account_financial_report_qweb/wizard/aged_partner_balance_wizard_view.xml @@ -31,6 +31,8 @@