diff --git a/account_financial_report_qweb/__openerp__.py b/account_financial_report_qweb/__openerp__.py
index e65be44d..33b23c0f 100644
--- a/account_financial_report_qweb/__openerp__.py
+++ b/account_financial_report_qweb/__openerp__.py
@@ -17,6 +17,7 @@
'account_full_reconcile',
'date_range',
'account_fiscal_year',
+ 'report_xlsx',
],
'data': [
'wizard/aged_partner_balance_wizard_view.xml',
diff --git a/account_financial_report_qweb/report/__init__.py b/account_financial_report_qweb/report/__init__.py
index 329caec3..25a4298d 100644
--- a/account_financial_report_qweb/report/__init__.py
+++ b/account_financial_report_qweb/report/__init__.py
@@ -5,3 +5,4 @@
from . import common
from . import general_ledger
from . import open_invoice
+from . import general_ledger_xlsx
diff --git a/account_financial_report_qweb/report/general_ledger_xlsx.py b/account_financial_report_qweb/report/general_ledger_xlsx.py
new file mode 100644
index 00000000..9247e497
--- /dev/null
+++ b/account_financial_report_qweb/report/general_ledger_xlsx.py
@@ -0,0 +1,95 @@
+# -*- coding: utf-8 -*-
+# Author: Damien Crier
+# Copyright 2016 Camptocamp SA
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+
+import logging
+
+from openerp.addons.report_xlsx.report.report_xlsx import ReportXlsx
+from openerp.report import report_sxw
+
+from ..wizard.ledger_report_wizard import FIELDS_TO_READ
+
+_logger = logging.getLogger(__name__)
+
+FIELDS_TO_READ_INV = {v: k for k, v in FIELDS_TO_READ.iteritems()}
+
+
+class GeneralLedgerXslx(ReportXlsx):
+
+ def __init__(self, name, table, rml=False, parser=False, header=True,
+ store=False):
+ super(GeneralLedgerXslx, self).__init__(
+ name, table, rml, parser, header, store)
+
+ def generate_xlsx_report(self, workbook, data, objects):
+
+ data = objects.compute()
+ report_name = data['header'][0].get('title', 'GeneralLedgerXslx')
+ sheet = workbook.add_worksheet(report_name[:31])
+ row_pos = 0
+ col_pos = 0
+
+ sheet.set_column(col_pos, col_pos, 30)
+ bold = workbook.add_format({'bold': True})
+ header_format = workbook.add_format({'bold': True,
+ 'align': 'center',
+ 'border': True,
+ 'bg_color': '#FFFFCC'})
+ std_cell_format = workbook.add_format({'bold': False,
+ 'align': 'left',
+ 'border': False,
+ 'bg_color': '#FFFFFF'})
+ # write report title on first cell A0 in XLSX sheet
+ sheet.write(row_pos, 0, report_name, bold)
+ row_pos += 2
+
+ # write filters taken for this report
+ col = 1
+ for k, v in data['header'][0].get('filters').iteritems():
+ sheet.write(row_pos, col, k, header_format)
+ sheet.write(row_pos+1, col, v, header_format)
+ col += 2
+
+ row_pos += 5
+
+ # write header line
+ col = 0
+ content = data['content'][0]
+
+ header_done = False
+ sorted_acc = content.keys()
+ sorted_acc.sort()
+ for acc in sorted_acc:
+ for move_lines in content[acc]:
+ # write line with account name
+ # sheet.write(row_pos, col+1, acc, bold)
+ # row_pos += 1
+ # write column headers
+ if not header_done:
+ for k, v in move_lines.iteritems():
+ position = FIELDS_TO_READ_INV.get(k, 'remove')
+ if position == 'remove':
+ continue
+ sheet.write(row_pos, position, k, bold)
+ # col2 += 1
+ row_pos += 1
+ header_done = True
+
+ for k, v in move_lines.iteritems():
+ if isinstance(v, (list, tuple)):
+ v = v[1]
+ position = FIELDS_TO_READ_INV.get(k, 'remove')
+ if position == 'remove':
+ continue
+ if position == 0:
+ sheet.write(row_pos, position, v, bold)
+ else:
+ sheet.write(row_pos, position, v, std_cell_format)
+
+ # col2 += 1
+ row_pos += 1
+
+
+GeneralLedgerXslx('report.ledger.report.wizard.xlsx',
+ 'ledger.report.wizard', parser=report_sxw.rml_parse)
diff --git a/account_financial_report_qweb/reports.xml b/account_financial_report_qweb/reports.xml
index e56ae668..08a2ae2d 100644
--- a/account_financial_report_qweb/reports.xml
+++ b/account_financial_report_qweb/reports.xml
@@ -36,5 +36,14 @@
+
+ General Ledger XLSX report
+ ledger.report.wizard
+ ir.actions.report.xml
+ ledger.report.wizard.xlsx
+ xlsx
+
+
+
diff --git a/account_financial_report_qweb/wizard/general_ledger_wizard.xml b/account_financial_report_qweb/wizard/general_ledger_wizard.xml
index b87159bc..9172a168 100644
--- a/account_financial_report_qweb/wizard/general_ledger_wizard.xml
+++ b/account_financial_report_qweb/wizard/general_ledger_wizard.xml
@@ -33,6 +33,8 @@
diff --git a/account_financial_report_qweb/wizard/ledger_report_wizard.py b/account_financial_report_qweb/wizard/ledger_report_wizard.py
index 035d7110..50e3c200 100644
--- a/account_financial_report_qweb/wizard/ledger_report_wizard.py
+++ b/account_financial_report_qweb/wizard/ledger_report_wizard.py
@@ -2,8 +2,29 @@
# Author: Damien Crier
# Copyright 2016 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+from operator import itemgetter
+
from openerp import models, fields, api, _
+# order to be placed on the report: field
+FIELDS_TO_READ = {1: 'date',
+ 0: 'account_id',
+ 4: 'account_code',
+ 2: 'move_name',
+ 3: 'journal_id',
+ 5: 'partner_name',
+ 6: 'ref',
+ 7: 'label',
+ 8: 'debit',
+ 9: 'credit',
+ 30: 'amount_currency',
+ 40: 'currency_code',
+ 50: 'month',
+ 60: 'partner_ref',
+ 10: 'cumul_balance',
+ 70: 'init_balance',
+ }
+
class LedgerReportWizard(models.TransientModel):
"""Base ledger report wizard."""
@@ -215,3 +236,154 @@ class LedgerReportWizardLine(models.TransientModel):
invoice_number = fields.Char()
centralized = fields.Boolean()
+
+ @api.multi
+ def check_report_xlsx(self):
+ self.ensure_one()
+ data = {}
+ data['ids'] = self.env.context.get('active_ids', [])
+ # data['model'] = 'general.ledger.line'
+ data['model'] = self.env.context.get('active_model', 'ir.ui.menu')
+ data['form'] = self.read(['date_from', 'date_to',
+ 'journal_ids', 'target_move'])[0]
+ used_context = self._build_contexts(data)
+ data['form']['used_context'] = dict(
+ used_context,
+ lang=self.env.context.get('lang', 'en_US'))
+ return self._print_report_xlsx(data)
+
+ @api.multi
+ def _print_report_xlsx(self, data):
+ return {
+ 'name': 'export xlsx general ledger',
+ 'model': 'ledger.report.wizard',
+ 'type': 'ir.actions.report.xml',
+ 'report_name': 'ledger.report.wizard.xlsx',
+ 'report_type': 'xlsx',
+ 'context': self.env.context,
+ }
+
+ @api.multi
+ def _get_centralized_move_ids(self, domain):
+ """ Get last line of each selected centralized accounts """
+ # inverse search on centralized boolean to finish the search to get the
+ # ids of last lines of centralized accounts
+ # XXX USE DISTINCT to speed up ?
+ domain = domain[:]
+ centralize_index = domain.index(('centralized', '=', False))
+ domain[centralize_index] = ('centralized', '=', True)
+
+ gl_lines = self.env['general.ledger.line'].search(domain)
+ accounts = gl_lines.mapped('account_id')
+
+ line_ids = []
+ for acc in accounts:
+ acc_lines = gl_lines.filtered(lambda rec: rec.account_id == acc)
+ line_ids.append(acc_lines[-1].id)
+ return line_ids
+
+ @api.multi
+ def _get_moves_from_dates_domain(self):
+ """ Prepare domain for `_get_moves_from_dates` """
+ domain = []
+ if self.centralize:
+ domain = [('centralized', '=', False)]
+ start_date = self.date_from
+ end_date = self.date_to
+ if start_date:
+ domain += [('date', '>=', start_date)]
+ if end_date:
+ domain += [('date', '<=', end_date)]
+
+ if self.target_move == 'posted':
+ domain += [('move_state', '=', 'posted')]
+
+ if self.account_ids:
+ domain += [('account_id', 'in', self.account_ids.ids)]
+
+ return domain
+
+ def compute_domain(self):
+ ret = self._get_moves_from_dates_domain()
+ if self.centralize:
+ centralized_ids = self._get_centralized_move_ids(ret)
+ if centralized_ids:
+ ret.insert(0, '|')
+ ret.append(('id', 'in', centralized_ids))
+ return ret
+
+ def initial_balance_line(self, amount, account_name, account_code, date):
+ return {'date': date,
+ 'account_id': account_name,
+ 'account_code': account_code,
+ 'move_name': '',
+ 'journal_id': '',
+ 'partner_name': '',
+ 'ref': '',
+ 'label': _('Initial Balance'),
+ 'debit': '',
+ 'credit': '',
+ 'amount_currency': '',
+ 'currency_code': '',
+ 'month': '',
+ 'partner_ref': '',
+ 'cumul_balance': amount,
+ 'init_balance': ''}
+
+ def group_general_ledger(self, report_lines, date_start):
+ """
+ group lines by account and order by account then date
+ """
+ result = {}
+ accounts = report_lines.mapped('account_id')
+ for account in accounts:
+ lines = report_lines.filtered(
+ lambda a: a.account_id.id == account.id)
+ acc_full_name = account.name_get()[0][1]
+ sorted_lines = sorted(lines.read(FIELDS_TO_READ.values()),
+ key=itemgetter('date'))
+ initial_balance = sorted_lines[0]['init_balance']
+ sorted_lines.insert(0, self.initial_balance_line(initial_balance,
+ acc_full_name,
+ account.code,
+ date_start))
+ result[acc_full_name] = sorted_lines
+
+ return result
+
+ def construct_header(self):
+ result = {}
+
+ result['title'] = _('General Ledger')
+ filters = {}
+
+ filters['centralized'] = _('%s' % self.centralize)
+ filters['start_date'] = self.date_from
+ filters['end_date'] = self.date_to
+
+ filters['target_moves'] = self.target_move
+
+ filters['accounts'] = _('All')
+ if self.account_ids:
+ filters['accounts'] = ', '.join([a.code for a in self.account_ids])
+
+ result['filters'] = filters
+
+ return result
+
+ @api.multi
+ def compute(self):
+ self.ensure_one()
+ # header filled with a dict
+ header = []
+ header.append(self.construct_header())
+ # content filled with dicts
+ content = []
+
+ domain = self.compute_domain()
+ report_lines = self.env['general.ledger.line'].search(domain)
+ lines_general_ledger = self.group_general_ledger(report_lines,
+ self.date_from)
+ content.append(lines_general_ledger)
+ return {'header': header,
+ 'content': content}