Browse Source

Merge pull request #367 from Eficent/11.0-mig-account_financial_report_qweb

[11.0][mig] account_financial_report
pull/372/head
Jordi Ballester Alomar 7 years ago
committed by GitHub
parent
commit
a4fa97d10f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .gitignore
  2. 72
      account_financial_report/README.rst
  3. 8
      account_financial_report/__init__.py
  4. 45
      account_financial_report/__manifest__.py
  5. 1365
      account_financial_report/i18n/account_financial_report.pot
  6. 87
      account_financial_report/menuitems.xml
  7. 6
      account_financial_report/models/__init__.py
  8. 14
      account_financial_report/models/account.py
  9. 15
      account_financial_report/report/__init__.py
  10. 301
      account_financial_report/report/abstract_report_xlsx.py
  11. 628
      account_financial_report/report/aged_partner_balance.py
  12. 257
      account_financial_report/report/aged_partner_balance_xlsx.py
  13. 1431
      account_financial_report/report/general_ledger.py
  14. 137
      account_financial_report/report/general_ledger_xlsx.py
  15. 776
      account_financial_report/report/open_items.py
  16. 105
      account_financial_report/report/open_items_xlsx.py
  17. 432
      account_financial_report/report/templates/aged_partner_balance.xml
  18. 263
      account_financial_report/report/templates/general_ledger.xml
  19. 36
      account_financial_report/report/templates/layouts.xml
  20. 233
      account_financial_report/report/templates/open_items.xml
  21. 155
      account_financial_report/report/templates/trial_balance.xml
  22. 263
      account_financial_report/report/trial_balance.py
  23. 122
      account_financial_report/report/trial_balance_xlsx.py
  24. 151
      account_financial_report/reports.xml
  25. BIN
      account_financial_report/static/description/icon.png
  26. 105
      account_financial_report/static/src/css/report.css
  27. 109
      account_financial_report/static/src/js/account_financial_report_backend.js
  28. 37
      account_financial_report/static/src/js/account_financial_report_widgets.js
  29. 9
      account_financial_report/tests/__init__.py
  30. 246
      account_financial_report/tests/abstract_test.py
  31. 42
      account_financial_report/tests/test_aged_partner_balance.py
  32. 466
      account_financial_report/tests/test_general_ledger.py
  33. 41
      account_financial_report/tests/test_open_items.py
  34. 54
      account_financial_report/tests/test_trial_balance.py
  35. 42
      account_financial_report/view/account_view.xml
  36. 9
      account_financial_report/view/report_aged_partner_balance.xml
  37. 9
      account_financial_report/view/report_general_ledger.xml
  38. 9
      account_financial_report/view/report_open_items.xml
  39. 45
      account_financial_report/view/report_template.xml
  40. 9
      account_financial_report/view/report_trial_balance.xml
  41. 9
      account_financial_report/wizard/__init__.py
  42. 103
      account_financial_report/wizard/aged_partner_balance_wizard.py
  43. 53
      account_financial_report/wizard/aged_partner_balance_wizard_view.xml
  44. 163
      account_financial_report/wizard/general_ledger_wizard.py
  45. 73
      account_financial_report/wizard/general_ledger_wizard_view.xml
  46. 111
      account_financial_report/wizard/open_items_wizard.py
  47. 53
      account_financial_report/wizard/open_items_wizard_view.xml
  48. 155
      account_financial_report/wizard/trial_balance_wizard.py
  49. 70
      account_financial_report/wizard/trial_balance_wizard_view.xml
  50. 2
      account_tax_balance/models/account_tax.py
  51. 3
      oca_dependencies.txt
  52. 2
      requirements.txt

1
.gitignore

@ -39,6 +39,7 @@ coverage.xml
# Pycharm
.idea
.vscode
# Mr Developer
.mr.developer.cfg

72
account_financial_report/README.rst

@ -0,0 +1,72 @@
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
:target: https://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
======================
QWeb Financial Reports
======================
This module adds a set of financial reports. They are accessible under
Accounting / Reporting / OCA Reports.
- General ledger
- Trial Balance
- Open Items
- Aged Partner Balance
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/91/11.0
Bug Tracker
===========
Bugs are tracked on `GitHub Issues
<https://github.com/OCA/account-financial-reporting/issues>`_. In case of trouble, please
check there if your issue has already been reported. If you spotted it first,
help us smashing it by providing a detailed and welcomed feedback.
Credits
=======
Images
------
* Odoo Community Association: `Icon <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_.
Contributors
------------
* Jordi Ballester <jordi.ballester@eficient.com>
* Yannick Vaucher <yannick.vaucher@camptocamp.com>
* Simone Orsi <simone.orsi@abstract.com>
* Leonardo Pistone <leonardo.pistone@camptocamp.com>
* Damien Crier <damien.crier@camptocamp.com>
* Andrea Stirpe <a.stirpe@onestein.nl>
* Thomas Rehn <thomas.rehn@initos.com>
* Andrea Gallina <4everamd@gmail.com>
* Robert Rottermann <robert@redcor.ch>
* Ciro Urselli <c.urselli@apuliasoftware.it>
* Francesco Apruzzese <opencode@e-ware.org>
* Lorenzo Battistini <lorenzo.battistini@agilebg.com>
* Julien Coux <julien.coux@camptocamp.com>
* Akim Juillerat <akim.juillerat@camptocamp.com>
* Alexis de Lattre <alexis@via.ecp.fr>
Much of the work in this module was done at a sprint in Sorrento, Italy in
April 2016.
Maintainer
----------
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
This module is maintained by the OCA.
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
To contribute to this module, please visit https://odoo-community.org.

8
account_financial_report/__init__.py

@ -0,0 +1,8 @@
# Author: Damien Crier
# Copyright 2016 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import models
from . import report
from . import wizard

45
account_financial_report/__manifest__.py

@ -0,0 +1,45 @@
# Author: Damien Crier
# Author: Julien Coux
# Copyright 2016 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{
'name': 'Account Financial Reports',
'version': '11.0.1.1.0',
'category': 'Reporting',
'summary': 'OCA Financial Reports',
'author': 'Camptocamp SA,'
'initOS GmbH,'
'redCOR AG,'
'Odoo Community Association (OCA)',
"website": "https://odoo-community.org/",
'depends': [
'account',
'date_range',
'account_fiscal_year',
'report_xlsx',
],
'data': [
'wizard/aged_partner_balance_wizard_view.xml',
'wizard/general_ledger_wizard_view.xml',
'wizard/open_items_wizard_view.xml',
'wizard/trial_balance_wizard_view.xml',
'menuitems.xml',
'reports.xml',
'report/templates/layouts.xml',
'report/templates/aged_partner_balance.xml',
'report/templates/general_ledger.xml',
'report/templates/open_items.xml',
'report/templates/trial_balance.xml',
'view/account_view.xml',
'view/report_template.xml',
'view/report_general_ledger.xml',
'view/report_trial_balance.xml',
'view/report_open_items.xml',
'view/report_aged_partner_balance.xml',
],
'installable': True,
'application': True,
'auto_install': False,
'license': 'AGPL-3',
}

1365
account_financial_report/i18n/account_financial_report.pot
File diff suppressed because it is too large
View File

87
account_financial_report/menuitems.xml

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<menuitem
parent="account.menu_finance_reports"
id="menu_oca_reports"
name="OCA accounting reports"
groups="account.group_account_manager,account.group_account_user"
/>
<menuitem
parent="menu_oca_reports"
action="action_general_ledger_wizard"
id="menu_general_ledger_wizard"
sequence="10"
/>
<menuitem
parent="menu_oca_reports"
action="action_trial_balance_wizard"
id="menu_trial_balance_wizard"
sequence="20"
/>
<menuitem
parent="menu_oca_reports"
action="action_open_items_wizard"
id="menu_open_items_wizard"
sequence="30"
/>
<menuitem
parent="menu_oca_reports"
action="action_aged_partner_balance_wizard"
id="menu_aged_partner_balance_wizard"
sequence="40"
/>
<!-- Hide odoo PDF reports menu -->
<menuitem
id="account.menu_finance_legal_statement"
name="PDF Reports"
parent="account.menu_finance_reports"
groups="base.group_erp_manager"
/>
<menuitem
id="account.menu_general_ledger"
name="General Ledger"
parent="account.menu_finance_legal_statement"
action="account.action_account_general_ledger_menu"
groups="base.group_erp_manager"
/>
<menuitem
id="account.menu_general_Balance_report"
name="Trial Balance"
parent="account.menu_finance_legal_statement"
action="account.action_account_balance_menu"
groups="base.group_erp_manager"
/>
<menuitem
id="account.menu_account_report_bs"
name="Balance Sheet"
action="account.action_account_report_bs"
parent="account.menu_finance_legal_statement"
groups="base.group_erp_manager"
/>
<menuitem
id="account.menu_account_report_pl"
name="Profit and Loss"
action="account.action_account_report_pl"
parent="account.menu_finance_legal_statement"
groups="base.group_erp_manager"
/>
<menuitem
id="account.menu_aged_trial_balance"
name="Aged Partner Balance"
action="account.action_account_aged_balance_view"
parent="account.menu_finance_legal_statement"
groups="base.group_erp_manager"
/>
</odoo>

6
account_financial_report/models/__init__.py

@ -0,0 +1,6 @@
# Author: Damien Crier
# Copyright 2016 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import account

14
account_financial_report/models/account.py

@ -0,0 +1,14 @@
# © 2011 Guewen Baconnier (Camptocamp)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).-
from odoo import models, fields
class AccountAccount(models.Model):
_inherit = 'account.account'
centralized = fields.Boolean(
'Centralized',
help="If flagged, no details will be displayed in "
"the General Ledger report (the webkit one only), "
"only centralized amounts per period.")

15
account_financial_report/report/__init__.py

@ -0,0 +1,15 @@
# © 2015 Yannick Vaucher (Camptocamp)
# © 2016 Damien Crier (Camptocamp)
# © 2016 Julien Coux (Camptocamp)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).-
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
from . import open_items_xlsx
from . import trial_balance
from . import trial_balance_xlsx

301
account_financial_report/report/abstract_report_xlsx.py

@ -0,0 +1,301 @@
# Author: Julien Coux
# Copyright 2016 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import models
class AbstractReportXslx(models.AbstractModel):
_name = 'report.account_financial_report.abstract_report_xlsx'
_inherit = 'report.report_xlsx.abstract'
def __init__(self, pool, cr):
# main sheet which will contains report
self.sheet = None
# columns of the report
self.columns = None
# row_pos must be incremented at each writing lines
self.row_pos = None
# 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 get_workbook_options(self):
return {'constant_memory': True}
def generate_xlsx_report(self, workbook, data, objects):
report = objects
self.row_pos = 0
self._define_formats(workbook)
report_name = self._get_report_name()
filters = self._get_report_filters(report)
self.columns = self._get_report_columns(report)
self.sheet = workbook.add_worksheet(report_name[:31])
self._set_column_width()
self._write_report_title(report_name)
self._write_filters(filters)
self._generate_report_content(workbook, report)
def _define_formats(self, workbook):
""" Add cell formats to current workbook.
Those formats can be used on all cell.
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,
'bg_color': '#FFFFCC'})
self.format_header_center = workbook.add_format(
{'bold': True,
'align': 'center',
'border': True,
'bg_color': '#FFFFCC'})
self.format_header_right = workbook.add_format(
{'bold': True,
'align': 'right',
'border': True,
'bg_color': '#FFFFCC'})
self.format_header_amount = workbook.add_format(
{'bold': True,
'border': True,
'bg_color': '#FFFFCC'})
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.
Columns are defined with `_get_report_columns` method.
"""
for position, column in self.columns.items():
self.sheet.set_column(position, position, column['width'])
def _write_report_title(self, title):
"""Write report title on current line using all defined columns width.
Columns are defined with `_get_report_columns` method.
"""
self.sheet.merge_range(
self.row_pos, 0, self.row_pos, len(self.columns) - 1,
title, self.format_bold
)
self.row_pos += 3
def _write_filters(self, filters):
"""Write one line per filters on starting on current line.
Columns number for filter name is defined
with `_get_col_count_filter_name` method.
Columns number for filter value is define
with `_get_col_count_filter_value` method.
"""
col_name = 1
col_count_filter_name = self._get_col_count_filter_name()
col_count_filter_value = self._get_col_count_filter_value()
col_value = col_name + col_count_filter_name + 1
for title, value in filters:
self.sheet.merge_range(
self.row_pos, col_name,
self.row_pos, col_name + col_count_filter_name - 1,
title, self.format_header_left)
self.sheet.merge_range(
self.row_pos, col_value,
self.row_pos, col_value + col_count_filter_value - 1,
value)
self.row_pos += 1
self.row_pos += 2
def write_array_title(self, title):
"""Write array title on current line using all defined columns width.
Columns are defined with `_get_report_columns` method.
"""
self.sheet.merge_range(
self.row_pos, 0, self.row_pos, len(self.columns) - 1,
title, self.format_bold
)
self.row_pos += 1
def write_array_header(self):
"""Write array header on current line using all defined columns name.
Columns are defined with `_get_report_columns` method.
"""
for col_pos, column in self.columns.items():
self.sheet.write(self.row_pos, col_pos, column['header'],
self.format_header_center)
self.row_pos += 1
def write_line(self, line_object):
"""Write a line on current line using all defined columns field name.
Columns are defined with `_get_report_columns` method.
"""
for col_pos, column in self.columns.items():
value = getattr(line_object, column['field'])
cell_type = column.get('type', 'string')
if cell_type == 'string':
self.sheet.write_string(self.row_pos, col_pos, value or '')
elif cell_type == 'amount':
self.sheet.write_number(
self.row_pos, col_pos, float(value), self.format_amount
)
self.row_pos += 1
def write_initial_balance(self, my_object, label):
"""Write a specific initial balance line on current line
using defined columns field_initial_balance name.
Columns are defined with `_get_report_columns` method.
"""
col_pos_label = self._get_col_pos_initial_balance_label()
self.sheet.write(self.row_pos, col_pos_label, label, self.format_right)
for col_pos, column in self.columns.items():
if column.get('field_initial_balance'):
value = getattr(my_object, column['field_initial_balance'])
cell_type = column.get('type', 'string')
if cell_type == 'string':
self.sheet.write_string(self.row_pos, col_pos, value or '')
elif cell_type == 'amount':
self.sheet.write_number(
self.row_pos, col_pos, float(value), self.format_amount
)
self.row_pos += 1
def write_ending_balance(self, my_object, name, label):
"""Write a specific ending balance line on current line
using defined columns field_final_balance name.
Columns are defined with `_get_report_columns` method.
"""
for i in range(0, len(self.columns)):
self.sheet.write(self.row_pos, i, '', self.format_header_right)
row_count_name = self._get_col_count_final_balance_name()
col_pos_label = self._get_col_pos_final_balance_label()
self.sheet.merge_range(
self.row_pos, 0, self.row_pos, row_count_name - 1, name,
self.format_header_left
)
self.sheet.write(self.row_pos, col_pos_label, label,
self.format_header_right)
for col_pos, column in self.columns.items():
if column.get('field_final_balance'):
value = getattr(my_object, column['field_final_balance'])
cell_type = column.get('type', 'string')
if cell_type == 'string':
self.sheet.write_string(self.row_pos, col_pos, value or '',
self.format_header_right)
elif cell_type == 'amount':
self.sheet.write_number(
self.row_pos, col_pos, float(value),
self.format_header_amount
)
self.row_pos += 1
def _generate_report_content(self, workbook, report):
pass
def _get_report_name(self):
"""
Allow to define the report name.
Report name will be used as sheet name and as report title.
:return: the report name
"""
raise NotImplementedError()
def _get_report_columns(self, report):
"""
Allow to define the report columns
which will be used to generate report.
:return: the report columns as dict
:Example:
{
0: {'header': 'Simple column',
'field': 'field_name_on_my_object',
'width': 11},
1: {'header': 'Amount column',
'field': 'field_name_on_my_object',
'type': 'amount',
'width': 14},
}
"""
raise NotImplementedError()
def _get_report_filters(self, report):
"""
:return: the report filters as list
:Example:
[
['first_filter_name', 'first_filter_value'],
['second_filter_name', 'second_filter_value']
]
"""
raise NotImplementedError()
def _get_col_count_filter_name(self):
"""
:return: the columns number used for filter names.
"""
raise NotImplementedError()
def _get_col_count_filter_value(self):
"""
:return: the columns number used for filter values.
"""
raise NotImplementedError()
def _get_col_pos_initial_balance_label(self):
"""
:return: the columns position used for initial balance label.
"""
raise NotImplementedError()
def _get_col_count_final_balance_name(self):
"""
:return: the columns number used for final balance name.
"""
raise NotImplementedError()
def _get_col_pos_final_balance_label(self):
"""
:return: the columns position used for final balance label.
"""
raise NotImplementedError()

628
account_financial_report/report/aged_partner_balance.py

@ -0,0 +1,628 @@
# © 2016 Julien Coux (Camptocamp)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import models, fields, api
class AgedPartnerBalanceReport(models.TransientModel):
""" Here, we just define class fields.
For methods, go more bottom at this file.
The class hierarchy is :
* AgedPartnerBalanceReport
** AgedPartnerBalanceReportAccount
*** AgedPartnerBalanceReportPartner
**** AgedPartnerBalanceReportLine
**** AgedPartnerBalanceReportMoveLine
If "show_move_line_details" is selected
"""
_name = 'report_aged_partner_balance'
# Filters fields, used for data computation
date_at = fields.Date()
only_posted_moves = fields.Boolean()
company_id = fields.Many2one(comodel_name='res.company')
filter_account_ids = fields.Many2many(comodel_name='account.account')
filter_partner_ids = fields.Many2many(comodel_name='res.partner')
show_move_line_details = fields.Boolean()
# Open Items Report Data fields, used as base for compute the data reports
open_items_id = fields.Many2one(comodel_name='report_open_items')
# Data fields, used to browse report data
account_ids = fields.One2many(
comodel_name='report_aged_partner_balance_account',
inverse_name='report_id'
)
class AgedPartnerBalanceReportAccount(models.TransientModel):
_name = 'report_aged_partner_balance_account'
_order = 'code ASC'
report_id = fields.Many2one(
comodel_name='report_aged_partner_balance',
ondelete='cascade',
index=True
)
# Data fields, used to keep link with real object
account_id = fields.Many2one(
'account.account',
index=True
)
# Data fields, used for report display
code = fields.Char()
name = fields.Char()
cumul_amount_residual = fields.Float(digits=(16, 2))
cumul_current = fields.Float(digits=(16, 2))
cumul_age_30_days = fields.Float(digits=(16, 2))
cumul_age_60_days = fields.Float(digits=(16, 2))
cumul_age_90_days = fields.Float(digits=(16, 2))
cumul_age_120_days = fields.Float(digits=(16, 2))
cumul_older = fields.Float(digits=(16, 2))
percent_current = fields.Float(digits=(16, 2))
percent_age_30_days = fields.Float(digits=(16, 2))
percent_age_60_days = fields.Float(digits=(16, 2))
percent_age_90_days = fields.Float(digits=(16, 2))
percent_age_120_days = fields.Float(digits=(16, 2))
percent_older = fields.Float(digits=(16, 2))
# Data fields, used to browse report data
partner_ids = fields.One2many(
comodel_name='report_aged_partner_balance_partner',
inverse_name='report_account_id'
)
class AgedPartnerBalanceReportPartner(models.TransientModel):
_name = 'report_aged_partner_balance_partner'
report_account_id = fields.Many2one(
comodel_name='report_aged_partner_balance_account',
ondelete='cascade',
index=True
)
# Data fields, used to keep link with real object
partner_id = fields.Many2one(
'res.partner',
index=True
)
# Data fields, used for report display
name = fields.Char()
# Data fields, used to browse report data
move_line_ids = fields.One2many(
comodel_name='report_aged_partner_balance_move_line',
inverse_name='report_partner_id'
)
line_ids = fields.One2many(
comodel_name='report_aged_partner_balance_line',
inverse_name='report_partner_id'
)
@api.model
def _generate_order_by(self, order_spec, query):
"""Custom order to display "No partner allocated" at last position."""
return """
ORDER BY
CASE
WHEN
"report_aged_partner_balance_partner"."partner_id" IS NOT NULL
THEN 0
ELSE 1
END,
"report_aged_partner_balance_partner"."name"
"""
class AgedPartnerBalanceReportLine(models.TransientModel):
_name = 'report_aged_partner_balance_line'
report_partner_id = fields.Many2one(
comodel_name='report_aged_partner_balance_partner',
ondelete='cascade',
index=True
)
# Data fields, used for report display
partner = fields.Char()
amount_residual = fields.Float(digits=(16, 2))
current = fields.Float(digits=(16, 2))
age_30_days = fields.Float(digits=(16, 2))
age_60_days = fields.Float(digits=(16, 2))
age_90_days = fields.Float(digits=(16, 2))
age_120_days = fields.Float(digits=(16, 2))
older = fields.Float(digits=(16, 2))
class AgedPartnerBalanceReportMoveLine(models.TransientModel):
_name = 'report_aged_partner_balance_move_line'
report_partner_id = fields.Many2one(
comodel_name='report_aged_partner_balance_partner',
ondelete='cascade',
index=True
)
# Data fields, used to keep link with real object
move_line_id = fields.Many2one('account.move.line')
# Data fields, used for report display
date = fields.Date()
date_due = fields.Date()
entry = fields.Char()
journal = fields.Char()
account = fields.Char()
partner = fields.Char()
label = fields.Char()
amount_residual = fields.Float(digits=(16, 2))
current = fields.Float(digits=(16, 2))
age_30_days = fields.Float(digits=(16, 2))
age_60_days = fields.Float(digits=(16, 2))
age_90_days = fields.Float(digits=(16, 2))
age_120_days = fields.Float(digits=(16, 2))
older = fields.Float(digits=(16, 2))
class AgedPartnerBalanceReportCompute(models.TransientModel):
""" Here, we just define methods.
For class fields, go more top at this file.
"""
_inherit = 'report_aged_partner_balance'
@api.multi
def print_report(self, report_type):
self.ensure_one()
if report_type == 'xlsx':
report_name = 'a_f_r.report_aged_partner_balance_xlsx'
else:
report_name = 'account_financial_report.' \
'report_aged_partner_balance_qweb'
report = self.env['ir.actions.report'].search(
[('report_name', '=', report_name),
('report_type', '=', report_type)], limit=1)
return report.report_action(self)
def _get_html(self):
result = {}
rcontext = {}
context = dict(self.env.context)
report = self.browse(context.get('active_id'))
if report:
rcontext['o'] = report
result['html'] = self.env.ref(
'account_financial_report.report_aged_partner_balance').render(
rcontext)
return result
@api.model
def get_html(self, given_context=None):
return self._get_html()
def _prepare_report_open_items(self):
self.ensure_one()
return {
'date_at': self.date_at,
'only_posted_moves': self.only_posted_moves,
'company_id': self.company_id.id,
'filter_account_ids': [(6, 0, self.filter_account_ids.ids)],
'filter_partner_ids': [(6, 0, self.filter_partner_ids.ids)],
}
@api.multi
def compute_data_for_report(self):
self.ensure_one()
# Compute Open Items Report Data.
# The data of Aged Partner Balance Report
# are based on Open Items Report data.
model = self.env['report_open_items']
self.open_items_id = model.create(self._prepare_report_open_items())
self.open_items_id.compute_data_for_report()
# Compute report data
self._inject_account_values()
self._inject_partner_values()
self._inject_line_values()
self._inject_line_values(only_empty_partner_line=True)
if self.show_move_line_details:
self._inject_move_line_values()
self._inject_move_line_values(only_empty_partner_line=True)
self._compute_accounts_cumul()
# Refresh cache because all data are computed with SQL requests
self.refresh()
def _inject_account_values(self):
"""Inject report values for report_aged_partner_balance_account"""
query_inject_account = """
INSERT INTO
report_aged_partner_balance_account
(
report_id,
create_uid,
create_date,
account_id,
code,
name
)
SELECT
%s AS report_id,
%s AS create_uid,
NOW() AS create_date,
rao.account_id,
rao.code,
rao.name
FROM
report_open_items_account rao
WHERE
rao.report_id = %s
"""
query_inject_account_params = (
self.id,
self.env.uid,
self.open_items_id.id,
)
self.env.cr.execute(query_inject_account, query_inject_account_params)
def _inject_partner_values(self):
"""Inject report values for report_aged_partner_balance_partner"""
query_inject_partner = """
INSERT INTO
report_aged_partner_balance_partner
(
report_account_id,
create_uid,
create_date,
partner_id,
name
)
SELECT
ra.id AS report_account_id,
%s AS create_uid,
NOW() AS create_date,
rpo.partner_id,
rpo.name
FROM
report_open_items_partner rpo
INNER JOIN
report_open_items_account rao ON rpo.report_account_id = rao.id
INNER JOIN
report_aged_partner_balance_account ra ON rao.code = ra.code
WHERE
rao.report_id = %s
AND ra.report_id = %s
"""
query_inject_partner_params = (
self.env.uid,
self.open_items_id.id,
self.id,
)
self.env.cr.execute(query_inject_partner, query_inject_partner_params)
def _inject_line_values(self, only_empty_partner_line=False):
""" Inject report values for report_aged_partner_balance_line.
The "only_empty_partner_line" value is used
to compute data without partner.
"""
query_inject_line = """
WITH
date_range AS
(
SELECT
%s AS date_current,
DATE %s - INTEGER '30' AS date_less_30_days,
DATE %s - INTEGER '60' AS date_less_60_days,
DATE %s - INTEGER '90' AS date_less_90_days,
DATE %s - INTEGER '120' AS date_less_120_days,
DATE %s - INTEGER '150' AS date_older
)
INSERT INTO
report_aged_partner_balance_line
(
report_partner_id,
create_uid,
create_date,
partner,
amount_residual,
current,
age_30_days,
age_60_days,
age_90_days,
age_120_days,
older
)
SELECT
rp.id AS report_partner_id,
%s AS create_uid,
NOW() AS create_date,
rp.name,
SUM(rlo.amount_residual) AS amount_residual,
SUM(
CASE
WHEN rlo.date_due > date_range.date_less_30_days
THEN rlo.amount_residual
END
) AS current,
SUM(
CASE
WHEN
rlo.date_due > date_range.date_less_60_days
AND rlo.date_due <= date_range.date_less_30_days
THEN rlo.amount_residual
END
) AS age_30_days,
SUM(
CASE
WHEN
rlo.date_due > date_range.date_less_90_days
AND rlo.date_due <= date_range.date_less_60_days
THEN rlo.amount_residual
END
) AS age_60_days,
SUM(
CASE
WHEN
rlo.date_due > date_range.date_less_120_days
AND rlo.date_due <= date_range.date_less_90_days
THEN rlo.amount_residual
END
) AS age_90_days,
SUM(
CASE
WHEN
rlo.date_due > date_range.date_older
AND rlo.date_due <= date_range.date_less_120_days
THEN rlo.amount_residual
END
) AS age_120_days,
SUM(
CASE
WHEN rlo.date_due <= date_range.date_older
THEN rlo.amount_residual
END
) AS older
FROM
date_range,
report_open_items_move_line rlo
INNER JOIN
report_open_items_partner rpo ON rlo.report_partner_id = rpo.id
INNER JOIN
report_open_items_account rao ON rpo.report_account_id = rao.id
INNER JOIN
report_aged_partner_balance_account ra ON rao.code = ra.code
INNER JOIN
report_aged_partner_balance_partner rp
ON
ra.id = rp.report_account_id
"""
if not only_empty_partner_line:
query_inject_line += """
AND rpo.partner_id = rp.partner_id
"""
elif only_empty_partner_line:
query_inject_line += """
AND rpo.partner_id IS NULL
AND rp.partner_id IS NULL
"""
query_inject_line += """
WHERE
rao.report_id = %s
AND ra.report_id = %s
GROUP BY
rp.id
"""
query_inject_line_params = (self.date_at,) * 6
query_inject_line_params += (
self.env.uid,
self.open_items_id.id,
self.id,
)
self.env.cr.execute(query_inject_line, query_inject_line_params)
def _inject_move_line_values(self, only_empty_partner_line=False):
""" Inject report values for report_aged_partner_balance_move_line
The "only_empty_partner_line" value is used
to compute data without partner.
"""
query_inject_move_line = """
WITH
date_range AS
(
SELECT
%s AS date_current,
DATE %s - INTEGER '30' AS date_less_30_days,
DATE %s - INTEGER '60' AS date_less_60_days,
DATE %s - INTEGER '90' AS date_less_90_days,
DATE %s - INTEGER '120' AS date_less_120_days,
DATE %s - INTEGER '150' AS date_older
)
INSERT INTO
report_aged_partner_balance_move_line
(
report_partner_id,
create_uid,
create_date,
date,
date_due,
entry,
journal,
account,
partner,
label,
amount_residual,
current,
age_30_days,
age_60_days,
age_90_days,
age_120_days,
older
)
SELECT
rp.id AS report_partner_id,
%s AS create_uid,
NOW() AS create_date,
rlo.date,
rlo.date_due,
rlo.entry,
rlo.journal,
rlo.account,
rlo.partner,
rlo.label,
rlo.amount_residual AS amount_residual,
CASE
WHEN rlo.date_due > date_range.date_less_30_days
THEN rlo.amount_residual
END AS current,
CASE
WHEN
rlo.date_due > date_range.date_less_60_days
AND rlo.date_due <= date_range.date_less_30_days
THEN rlo.amount_residual
END AS age_30_days,
CASE
WHEN
rlo.date_due > date_range.date_less_90_days
AND rlo.date_due <= date_range.date_less_60_days
THEN rlo.amount_residual
END AS age_60_days,
CASE
WHEN
rlo.date_due > date_range.date_less_120_days
AND rlo.date_due <= date_range.date_less_90_days
THEN rlo.amount_residual
END AS age_90_days,
CASE
WHEN
rlo.date_due > date_range.date_older
AND rlo.date_due <= date_range.date_less_120_days
THEN rlo.amount_residual
END AS age_120_days,
CASE
WHEN rlo.date_due <= date_range.date_older
THEN rlo.amount_residual
END AS older
FROM
date_range,
report_open_items_move_line rlo
INNER JOIN
report_open_items_partner rpo ON rlo.report_partner_id = rpo.id
INNER JOIN
report_open_items_account rao ON rpo.report_account_id = rao.id
INNER JOIN
report_aged_partner_balance_account ra ON rao.code = ra.code
INNER JOIN
report_aged_partner_balance_partner rp
ON
ra.id = rp.report_account_id
"""
if not only_empty_partner_line:
query_inject_move_line += """
AND rpo.partner_id = rp.partner_id
"""
elif only_empty_partner_line:
query_inject_move_line += """
AND rpo.partner_id IS NULL
AND rp.partner_id IS NULL
"""
query_inject_move_line += """
WHERE
rao.report_id = %s
AND ra.report_id = %s
"""
query_inject_move_line_params = (self.date_at,) * 6
query_inject_move_line_params += (
self.env.uid,
self.open_items_id.id,
self.id,
)
self.env.cr.execute(query_inject_move_line,
query_inject_move_line_params)
def _compute_accounts_cumul(self):
""" Compute cumulative amount for
report_aged_partner_balance_account.
"""
query_compute_accounts_cumul = """
WITH
cumuls AS
(
SELECT
ra.id AS report_account_id,
SUM(rl.amount_residual) AS cumul_amount_residual,
SUM(rl.current) AS cumul_current,
SUM(rl.age_30_days) AS cumul_age_30_days,
SUM(rl.age_60_days) AS cumul_age_60_days,
SUM(rl.age_90_days) AS cumul_age_90_days,
SUM(rl.age_120_days) AS cumul_age_120_days,
SUM(rl.older) AS cumul_older
FROM
report_aged_partner_balance_line rl
INNER JOIN
report_aged_partner_balance_partner rp
ON rl.report_partner_id = rp.id
INNER JOIN
report_aged_partner_balance_account ra
ON rp.report_account_id = ra.id
WHERE
ra.report_id = %s
GROUP BY
ra.id
)
UPDATE
report_aged_partner_balance_account
SET
cumul_amount_residual = c.cumul_amount_residual,
cumul_current = c.cumul_current,
cumul_age_30_days = c.cumul_age_30_days,
cumul_age_60_days = c.cumul_age_60_days,
cumul_age_90_days = c.cumul_age_90_days,
cumul_age_120_days = c.cumul_age_120_days,
cumul_older = c.cumul_older,
percent_current =
CASE
WHEN c.cumul_amount_residual != 0
THEN 100 * c.cumul_current / c.cumul_amount_residual
END,
percent_age_30_days =
CASE
WHEN c.cumul_amount_residual != 0
THEN 100 * c.cumul_age_30_days / c.cumul_amount_residual
END,
percent_age_60_days =
CASE
WHEN c.cumul_amount_residual != 0
THEN 100 * c.cumul_age_60_days / c.cumul_amount_residual
END,
percent_age_90_days =
CASE
WHEN c.cumul_amount_residual != 0
THEN 100 * c.cumul_age_90_days / c.cumul_amount_residual
END,
percent_age_120_days =
CASE
WHEN c.cumul_amount_residual != 0
THEN 100 * c.cumul_age_120_days / c.cumul_amount_residual
END,
percent_older =
CASE
WHEN c.cumul_amount_residual != 0
THEN 100 * c.cumul_older / c.cumul_amount_residual
END
FROM
cumuls c
WHERE
id = c.report_account_id
"""
params_compute_accounts_cumul = (self.id,)
self.env.cr.execute(query_compute_accounts_cumul,
params_compute_accounts_cumul)

257
account_financial_report/report/aged_partner_balance_xlsx.py

@ -0,0 +1,257 @@
# Author: Julien Coux
# Copyright 2016 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import _, models
class AgedPartnerBalanceXslx(models.AbstractModel):
_name = 'report.a_f_r.report_aged_partner_balance_xlsx'
_inherit = 'report.account_financial_report.abstract_report_xlsx'
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.items():
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

1431
account_financial_report/report/general_ledger.py
File diff suppressed because it is too large
View File

137
account_financial_report/report/general_ledger_xlsx.py

@ -0,0 +1,137 @@
# Author: Damien Crier
# Author: Julien Coux
# Copyright 2016 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import _, models
class GeneralLedgerXslx(models.AbstractModel):
_name = 'report.a_f_r.report_general_ledger_xlsx'
_inherit = 'report.account_financial_report.abstract_report_xlsx'
def _get_report_name(self):
return _('General Ledger')
def _get_report_columns(self, report):
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': _('Cost center'),
'field': 'cost_center',
'width': 15},
7: {'header': _('Rec.'), 'field': 'matching_number', 'width': 5},
8: {'header': _('Debit'),
'field': 'debit',
'field_initial_balance': 'initial_debit',
'field_final_balance': 'final_debit',
'type': 'amount',
'width': 14},
9: {'header': _('Credit'),
'field': 'credit',
'field_initial_balance': 'initial_credit',
'field_final_balance': 'final_credit',
'type': 'amount',
'width': 14},
10: {'header': _('Cumul. Bal.'),
'field': 'cumul_balance',
'field_initial_balance': 'initial_balance',
'field_final_balance': 'final_balance',
'type': 'amount',
'width': 14},
11: {'header': _('Cur.'), 'field': 'currency_name', 'width': 7},
12: {'header': _('Amount cur.'),
'field': 'amount_currency',
'type': 'amount',
'width': 14},
}
def _get_report_filters(self, report):
return [
[_('Date range filter'),
_('From: %s To: %s') % (report.date_from, report.date_to)],
[_('Target moves filter'),
_('All posted entries') if report.only_posted_moves
else _('All entries')],
[_('Account balance at 0 filter'),
_('Hide') if report.hide_account_balance_at_0 else _('Show')],
[_('Centralize filter'),
_('Yes') if report.centralize else _('No')],
]
def _get_col_count_filter_name(self):
return 2
def _get_col_count_filter_value(self):
return 2
def _get_col_pos_initial_balance_label(self):
return 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):
# For each account
for account in report.account_ids:
# Write account title
self.write_array_title(account.code + ' - ' + account.name)
if not account.partner_ids:
# Display array header for move lines
self.write_array_header()
# Display initial balance line for account
self.write_initial_balance(account, _('Initial balance'))
# Display account move lines
for line in account.move_line_ids:
self.write_line(line)
else:
# 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 initial balance line for partner
self.write_initial_balance(partner, _('Initial balance'))
# 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, 'partner')
# Line break
self.row_pos += 1
# Display ending balance line for account
self.write_ending_balance(account, 'account')
# 2 lines break
self.row_pos += 2
def write_ending_balance(self, my_object, type_object):
"""Specific function to write ending balance for General Ledger"""
if type_object == 'partner':
name = my_object.name
label = _('Partner ending balance')
elif type_object == 'account':
name = my_object.code + ' - ' + my_object.name
label = _('Ending balance')
super(GeneralLedgerXslx, self).write_ending_balance(
my_object, name, label
)

776
account_financial_report/report/open_items.py

@ -0,0 +1,776 @@
# © 2016 Julien Coux (Camptocamp)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import models, fields, api, _
class OpenItemsReport(models.TransientModel):
""" Here, we just define class fields.
For methods, go more bottom at this file.
The class hierarchy is :
* OpenItemsReport
** OpenItemsReportAccount
*** OpenItemsReportPartner
**** OpenItemsReportMoveLine
"""
_name = 'report_open_items'
# Filters fields, used for data computation
date_at = fields.Date()
only_posted_moves = fields.Boolean()
hide_account_balance_at_0 = fields.Boolean()
company_id = fields.Many2one(comodel_name='res.company')
filter_account_ids = fields.Many2many(comodel_name='account.account')
filter_partner_ids = fields.Many2many(comodel_name='res.partner')
# Flag fields, used for report display
has_second_currency = fields.Boolean()
# Data fields, used to browse report data
account_ids = fields.One2many(
comodel_name='report_open_items_account',
inverse_name='report_id'
)
class OpenItemsReportAccount(models.TransientModel):
_name = 'report_open_items_account'
_order = 'code ASC'
report_id = fields.Many2one(
comodel_name='report_open_items',
ondelete='cascade',
index=True
)
# Data fields, used to keep link with real object
account_id = fields.Many2one(
'account.account',
index=True
)
# Data fields, used for report display
code = fields.Char()
name = fields.Char()
final_amount_residual = fields.Float(digits=(16, 2))
# Data fields, used to browse report data
partner_ids = fields.One2many(
comodel_name='report_open_items_partner',
inverse_name='report_account_id'
)
class OpenItemsReportPartner(models.TransientModel):
_name = 'report_open_items_partner'
report_account_id = fields.Many2one(
comodel_name='report_open_items_account',
ondelete='cascade',
index=True
)
# Data fields, used to keep link with real object
partner_id = fields.Many2one(
'res.partner',
index=True
)
# Data fields, used for report display
name = fields.Char()
final_amount_residual = fields.Float(digits=(16, 2))
# Data fields, used to browse report data
move_line_ids = fields.One2many(
comodel_name='report_open_items_move_line',
inverse_name='report_partner_id'
)
@api.model
def _generate_order_by(self, order_spec, query):
"""Custom order to display "No partner allocated" at last position."""
return """
ORDER BY
CASE
WHEN "report_open_items_partner"."partner_id" IS NOT NULL
THEN 0
ELSE 1
END,
"report_open_items_partner"."name"
"""
class OpenItemsReportMoveLine(models.TransientModel):
_name = 'report_open_items_move_line'
report_partner_id = fields.Many2one(
comodel_name='report_open_items_partner',
ondelete='cascade',
index=True
)
# Data fields, used to keep link with real object
move_line_id = fields.Many2one('account.move.line')
# Data fields, used for report display
date = fields.Date()
date_due = fields.Date()
entry = fields.Char()
journal = fields.Char()
account = fields.Char()
partner = fields.Char()
label = fields.Char()
amount_total_due = fields.Float(digits=(16, 2))
amount_residual = fields.Float(digits=(16, 2))
currency_name = fields.Char()
amount_total_due_currency = fields.Float(digits=(16, 2))
amount_residual_currency = fields.Float(digits=(16, 2))
class OpenItemsReportCompute(models.TransientModel):
""" Here, we just define methods.
For class fields, go more top at this file.
"""
_inherit = 'report_open_items'
@api.multi
def print_report(self, report_type):
self.ensure_one()
if report_type == 'xlsx':
report_name = 'a_f_r.report_open_items_xlsx'
else:
report_name = 'account_financial_report.' \
'report_open_items_qweb'
return self.env['ir.actions.report'].search(
[('report_name', '=', report_name),
('report_type', '=', report_type)], limit=1).report_action(self)
def _get_html(self):
result = {}
rcontext = {}
context = dict(self.env.context)
report = self.browse(context.get('active_id'))
if report:
rcontext['o'] = report
result['html'] = self.env.ref(
'account_financial_report.report_open_items').render(
rcontext)
return result
@api.model
def get_html(self, given_context=None):
return self._get_html()
@api.multi
def compute_data_for_report(self):
self.ensure_one()
# Compute report data
self._inject_account_values()
self._inject_partner_values()
self._inject_line_values()
self._inject_line_values(only_empty_partner_line=True)
self._clean_partners_and_accounts()
self._compute_partners_and_accounts_cumul()
if self.hide_account_balance_at_0:
self._clean_partners_and_accounts(
only_delete_account_balance_at_0=True
)
# Compute display flag
self._compute_has_second_currency()
# Refresh cache because all data are computed with SQL requests
self.refresh()
def _inject_account_values(self):
"""Inject report values for report_open_items_account."""
query_inject_account = """
WITH
accounts AS
(
SELECT
a.id,
a.code,
a.name,
a.user_type_id
FROM
account_account a
INNER JOIN
account_move_line ml ON a.id = ml.account_id AND ml.date <= %s
"""
if self.filter_partner_ids:
query_inject_account += """
INNER JOIN
res_partner p ON ml.partner_id = p.id
"""
if self.only_posted_moves:
query_inject_account += """
INNER JOIN
account_move m ON ml.move_id = m.id AND m.state = 'posted'
"""
query_inject_account += """
WHERE
a.company_id = %s
AND a.reconcile IS true
"""
if self.filter_account_ids:
query_inject_account += """
AND
a.id IN %s
"""
if self.filter_partner_ids:
query_inject_account += """
AND
p.id IN %s
"""
query_inject_account += """
GROUP BY
a.id
)
INSERT INTO
report_open_items_account
(
report_id,
create_uid,
create_date,
account_id,
code,
name
)
SELECT
%s AS report_id,
%s AS create_uid,
NOW() AS create_date,
a.id AS account_id,
a.code,
a.name
FROM
accounts a
"""
query_inject_account_params = (
self.date_at,
self.company_id.id,
)
if self.filter_account_ids:
query_inject_account_params += (
tuple(self.filter_account_ids.ids),
)
if self.filter_partner_ids:
query_inject_account_params += (
tuple(self.filter_partner_ids.ids),
)
query_inject_account_params += (
self.id,
self.env.uid,
)
self.env.cr.execute(query_inject_account, query_inject_account_params)
def _inject_partner_values(self):
""" Inject report values for report_open_items_partner. """
# pylint: disable=sql-injection
query_inject_partner = """
WITH
accounts_partners AS
(
SELECT
ra.id AS report_account_id,
a.id AS account_id,
at.include_initial_balance AS include_initial_balance,
p.id AS partner_id,
COALESCE(
CASE
WHEN
NULLIF(p.name, '') IS NOT NULL
AND NULLIF(p.ref, '') IS NOT NULL
THEN p.name || ' (' || p.ref || ')'
ELSE p.name
END,
'""" + _('No partner allocated') + """'
) AS partner_name
FROM
report_open_items_account ra
INNER JOIN
account_account a ON ra.account_id = a.id
INNER JOIN
account_account_type at ON a.user_type_id = at.id
INNER JOIN
account_move_line ml ON a.id = ml.account_id AND ml.date <= %s
"""
if self.only_posted_moves:
query_inject_partner += """
INNER JOIN
account_move m ON ml.move_id = m.id AND m.state = 'posted'
"""
query_inject_partner += """
LEFT JOIN
res_partner p ON ml.partner_id = p.id
WHERE
ra.report_id = %s
"""
if self.filter_partner_ids:
query_inject_partner += """
AND
p.id IN %s
"""
query_inject_partner += """
GROUP BY
ra.id,
a.id,
p.id,
at.include_initial_balance
)
INSERT INTO
report_open_items_partner
(
report_account_id,
create_uid,
create_date,
partner_id,
name
)
SELECT
ap.report_account_id,
%s AS create_uid,
NOW() AS create_date,
ap.partner_id,
ap.partner_name
FROM
accounts_partners ap
"""
query_inject_partner_params = (
self.date_at,
self.id,
)
if self.filter_partner_ids:
query_inject_partner_params += (
tuple(self.filter_partner_ids.ids),
)
query_inject_partner_params += (
self.env.uid,
)
self.env.cr.execute(query_inject_partner, query_inject_partner_params)
def _get_line_sub_query_move_lines(self,
only_empty_partner_line=False,
positive_balance=True):
""" Return subquery used to compute sum amounts on lines """
sub_query = """
SELECT
ml.id,
ml.balance,
SUM(
CASE
WHEN ml_past.id IS NOT NULL
THEN pr.amount
ELSE NULL
END
) AS partial_amount,
ml.amount_currency,
SUM(
CASE
WHEN ml_past.id IS NOT NULL
THEN pr.amount_currency
ELSE NULL
END
) AS partial_amount_currency,
ml.currency_id
FROM
report_open_items_partner rp
INNER JOIN
report_open_items_account ra
ON rp.report_account_id = ra.id
INNER JOIN
account_move_line ml
ON ra.account_id = ml.account_id
"""
if not only_empty_partner_line:
sub_query += """
AND rp.partner_id = ml.partner_id
"""
elif only_empty_partner_line:
sub_query += """
AND ml.partner_id IS NULL
"""
if not positive_balance:
sub_query += """
LEFT JOIN
account_partial_reconcile pr
ON ml.balance < 0 AND pr.credit_move_id = ml.id
LEFT JOIN
account_move_line ml_future
ON ml.balance < 0 AND pr.debit_move_id = ml_future.id
AND ml_future.date > %s
LEFT JOIN
account_move_line ml_past
ON ml.balance < 0 AND pr.debit_move_id = ml_past.id
AND ml_past.date <= %s
"""
else:
sub_query += """
LEFT JOIN
account_partial_reconcile pr
ON ml.balance > 0 AND pr.debit_move_id = ml.id
LEFT JOIN
account_move_line ml_future
ON ml.balance > 0 AND pr.credit_move_id = ml_future.id
AND ml_future.date > %s
LEFT JOIN
account_move_line ml_past
ON ml.balance > 0 AND pr.credit_move_id = ml_past.id
AND ml_past.date <= %s
"""
sub_query += """
WHERE
ra.report_id = %s
GROUP BY
ml.id,
ml.balance,
ml.amount_currency
HAVING
(
ml.full_reconcile_id IS NULL
OR MAX(ml_future.id) IS NOT NULL
)
"""
return sub_query
def _inject_line_values(self, only_empty_partner_line=False):
""" Inject report values for report_open_items_move_line.
The "only_empty_partner_line" value is used
to compute data without partner.
"""
query_inject_move_line = """
WITH
move_lines_amount AS
(
"""
query_inject_move_line += self._get_line_sub_query_move_lines(
only_empty_partner_line=only_empty_partner_line,
positive_balance=True
)
query_inject_move_line += """
UNION
"""
query_inject_move_line += self._get_line_sub_query_move_lines(
only_empty_partner_line=only_empty_partner_line,
positive_balance=False
)
query_inject_move_line += """
),
move_lines AS
(
SELECT
id,
CASE
WHEN SUM(partial_amount) > 0
THEN
CASE
WHEN balance > 0
THEN balance - SUM(partial_amount)
ELSE balance + SUM(partial_amount)
END
ELSE balance
END AS amount_residual,
CASE
WHEN SUM(partial_amount_currency) > 0
THEN
CASE
WHEN amount_currency > 0
THEN amount_currency - SUM(partial_amount_currency)
ELSE amount_currency + SUM(partial_amount_currency)
END
ELSE amount_currency
END AS amount_residual_currency,
currency_id
FROM
move_lines_amount
GROUP BY
id,
balance,
amount_currency,
currency_id
)
INSERT INTO
report_open_items_move_line
(
report_partner_id,
create_uid,
create_date,
move_line_id,
date,
date_due,
entry,
journal,
account,
partner,
label,
amount_total_due,
amount_residual,
currency_name,
amount_total_due_currency,
amount_residual_currency
)
SELECT
rp.id AS report_partner_id,
%s AS create_uid,
NOW() AS create_date,
ml.id AS move_line_id,
ml.date,
ml.date_maturity,
m.name AS entry,
j.code AS journal,
a.code AS account,
"""
if not only_empty_partner_line:
query_inject_move_line += """
CASE
WHEN
NULLIF(p.name, '') IS NOT NULL
AND NULLIF(p.ref, '') IS NOT NULL
THEN p.name || ' (' || p.ref || ')'
ELSE p.name
END AS partner,
"""
elif only_empty_partner_line:
query_inject_move_line += """
'""" + _('No partner allocated') + """' AS partner,
"""
query_inject_move_line += """
CONCAT_WS(' - ', NULLIF(ml.ref, ''), NULLIF(ml.name, '')) AS label,
ml.balance,
ml2.amount_residual,
c.name AS currency_name,
ml.amount_currency,
ml2.amount_residual_currency
FROM
report_open_items_partner rp
INNER JOIN
report_open_items_account ra ON rp.report_account_id = ra.id
INNER JOIN
account_move_line ml ON ra.account_id = ml.account_id
INNER JOIN
move_lines ml2
ON ml.id = ml2.id
AND ml2.amount_residual IS NOT NULL
AND ml2.amount_residual != 0
INNER JOIN
account_move m ON ml.move_id = m.id
INNER JOIN
account_journal j ON ml.journal_id = j.id
INNER JOIN
account_account a ON ml.account_id = a.id
"""
if not only_empty_partner_line:
query_inject_move_line += """
INNER JOIN
res_partner p
ON ml.partner_id = p.id AND rp.partner_id = p.id
"""
query_inject_move_line += """
LEFT JOIN
account_full_reconcile fr ON ml.full_reconcile_id = fr.id
LEFT JOIN
res_currency c ON ml2.currency_id = c.id
WHERE
ra.report_id = %s
AND
ml.date <= %s
"""
if self.only_posted_moves:
query_inject_move_line += """
AND
m.state = 'posted'
"""
if only_empty_partner_line:
query_inject_move_line += """
AND
ml.partner_id IS NULL
AND
rp.partner_id IS NULL
"""
if not only_empty_partner_line:
query_inject_move_line += """
ORDER BY
a.code, p.name, ml.date, ml.id
"""
elif only_empty_partner_line:
query_inject_move_line += """
ORDER BY
a.code, ml.date, ml.id
"""
self.env.cr.execute(
query_inject_move_line,
(self.date_at,
self.date_at,
self.id,
self.date_at,
self.date_at,
self.id,
self.env.uid,
self.id,
self.date_at,)
)
def _compute_partners_and_accounts_cumul(self):
""" Compute cumulative amount for
report_open_items_partner and report_open_items_account.
"""
query_compute_partners_cumul = """
UPDATE
report_open_items_partner
SET
final_amount_residual =
(
SELECT
SUM(rml.amount_residual) AS final_amount_residual
FROM
report_open_items_move_line rml
WHERE
rml.report_partner_id = report_open_items_partner.id
)
WHERE
id IN
(
SELECT
rp.id
FROM
report_open_items_account ra
INNER JOIN
report_open_items_partner rp
ON ra.id = rp.report_account_id
WHERE
ra.report_id = %s
)
"""
params_compute_partners_cumul = (self.id,)
self.env.cr.execute(query_compute_partners_cumul,
params_compute_partners_cumul)
query_compute_accounts_cumul = """
UPDATE
report_open_items_account
SET
final_amount_residual =
(
SELECT
SUM(rp.final_amount_residual) AS final_amount_residual
FROM
report_open_items_partner rp
WHERE
rp.report_account_id = report_open_items_account.id
)
WHERE
report_id = %s
"""
params_compute_accounts_cumul = (self.id,)
self.env.cr.execute(query_compute_accounts_cumul,
params_compute_accounts_cumul)
def _clean_partners_and_accounts(self,
only_delete_account_balance_at_0=False):
""" Delete empty data for
report_open_items_partner and report_open_items_account.
The "only_delete_account_balance_at_0" value is used
to delete also the data with cumulative amounts at 0.
"""
query_clean_partners = """
DELETE FROM
report_open_items_partner
WHERE
id IN
(
SELECT
DISTINCT rp.id
FROM
report_open_items_account ra
INNER JOIN
report_open_items_partner rp
ON ra.id = rp.report_account_id
LEFT JOIN
report_open_items_move_line rml
ON rp.id = rml.report_partner_id
WHERE
ra.report_id = %s
"""
if not only_delete_account_balance_at_0:
query_clean_partners += """
AND rml.id IS NULL
"""
elif only_delete_account_balance_at_0:
query_clean_partners += """
AND (
rp.final_amount_residual IS NULL
OR rp.final_amount_residual = 0
)
"""
query_clean_partners += """
)
"""
params_clean_partners = (self.id,)
self.env.cr.execute(query_clean_partners, params_clean_partners)
query_clean_accounts = """
DELETE FROM
report_open_items_account
WHERE
id IN
(
SELECT
DISTINCT ra.id
FROM
report_open_items_account ra
LEFT JOIN
report_open_items_partner rp
ON ra.id = rp.report_account_id
WHERE
ra.report_id = %s
"""
if not only_delete_account_balance_at_0:
query_clean_accounts += """
AND rp.id IS NULL
"""
elif only_delete_account_balance_at_0:
query_clean_accounts += """
AND (
ra.final_amount_residual IS NULL
OR ra.final_amount_residual = 0
)
"""
query_clean_accounts += """
)
"""
params_clean_accounts = (self.id,)
self.env.cr.execute(query_clean_accounts, params_clean_accounts)
def _compute_has_second_currency(self):
""" Compute "has_second_currency" flag which will used for display."""
query_update_has_second_currency = """
UPDATE
report_open_items
SET
has_second_currency =
(
SELECT
TRUE
FROM
report_open_items_move_line l
INNER JOIN
report_open_items_partner p
ON l.report_partner_id = p.id
INNER JOIN
report_open_items_account a
ON p.report_account_id = a.id
WHERE
a.report_id = %s
AND l.currency_name IS NOT NULL
LIMIT 1
)
WHERE id = %s
"""
params = (self.id,) * 2
self.env.cr.execute(query_update_has_second_currency, params)

105
account_financial_report/report/open_items_xlsx.py

@ -0,0 +1,105 @@
# Author: Julien Coux
# Copyright 2016 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import _, models
class OpenItemsXslx(models.AbstractModel):
_name = 'report.a_f_r.report_open_items_xlsx'
_inherit = 'report.account_financial_report.abstract_report_xlsx'
def _get_report_name(self):
return _('Open Items')
def _get_report_columns(self, report):
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': _('Original'),
'field': 'amount_total_due',
'type': 'amount',
'width': 14},
8: {'header': _('Residual'),
'field': 'amount_residual',
'field_final_balance': 'final_amount_residual',
'type': 'amount',
'width': 14},
9: {'header': _('Cur.'), 'field': 'currency_name', 'width': 7},
10: {'header': _('Cur. Original'),
'field': 'amount_total_due_currency',
'type': 'amount',
'width': 14},
11: {'header': _('Cur. Residual'),
'field': 'amount_residual_currency',
'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')],
[_('Account balance at 0 filter'),
_('Hide') if report.hide_account_balance_at_0 else _('Show')],
]
def _get_col_count_filter_name(self):
return 2
def _get_col_count_filter_value(self):
return 2
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):
# 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, 'partner')
# Line break
self.row_pos += 1
# Display ending balance line for account
self.write_ending_balance(account, 'account')
# 2 lines break
self.row_pos += 2
def write_ending_balance(self, my_object, type_object):
"""Specific function to write ending balance for Open Items"""
if type_object == 'partner':
name = my_object.name
label = _('Partner ending balance')
elif type_object == 'account':
name = my_object.code + ' - ' + my_object.name
label = _('Ending balance')
super(OpenItemsXslx, self).write_ending_balance(my_object, name, label)

432
account_financial_report/report/templates/aged_partner_balance.xml

@ -0,0 +1,432 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="account_financial_report.report_aged_partner_balance_qweb">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="o">
<t t-call="account_financial_report.internal_layout">
<t t-call="account_financial_report.report_aged_partner_balance_base"/>
</t>
</t>
</t>
</template>
<template id="account_financial_report.report_aged_partner_balance_base">
<!-- Saved flag fields into variables, used to define columns display -->
<t t-set="show_move_line_details" t-value="o.show_move_line_details"/>
<!-- Defines global variables used by internal layout -->
<t t-set="title">Aged Partner Balance</t>
<t t-set="company_name" t-value="o.company_id.name"/>
<div class="page">
<!-- Display filters -->
<t t-call="account_financial_report.report_aged_partner_balance_filters"/>
<t t-foreach="o.account_ids" t-as="account">
<div class="page_break">
<!-- Display account header -->
<div class="act_as_table list_table" style="margin-top: 10px;"/>
<div class="act_as_caption account_title" style="width: 1141px !important;">
<span t-field="account.code"/>
-
<span t-field="account.name"/>
</div>
<!-- Display account lines -->
<t t-if="not show_move_line_details">
<div class="act_as_table data_table" style="width: 1140px !important;">
<!-- Display account header -->
<t t-call="account_financial_report.report_aged_partner_balance_lines_header"/>
<t t-foreach="account.partner_ids" t-as="partner">
<!-- Display one line per partner -->
<t t-call="account_financial_report.report_aged_partner_balance_lines"/>
</t>
</div>
<!-- Display account footer -->
<t t-call="account_financial_report.report_aged_partner_balance_account_ending_cumul"/>
</t>
<!-- Display account move lines -->
<t t-if="show_move_line_details">
<!-- Display account partners -->
<t t-foreach="account.partner_ids" t-as="partner">
<div class="page_break">
<!-- Display partner header -->
<div class="act_as_caption account_title">
<span t-field="partner.name"/>
</div>
<!-- Display partner move lines -->
<t t-call="account_financial_report.report_aged_partner_balance_move_lines"/>
<!-- Display partner footer -->
<t t-call="account_financial_report.report_aged_partner_balance_partner_ending_cumul">
<t t-set="partner_cumul_line" t-value="partner.line_ids"/>
</t>
</div>
</t>
<!-- Display account footer -->
<t t-call="account_financial_report.report_aged_partner_balance_account_ending_cumul"/>
</t>
</div>
</t>
</div>
</template>
<template id="account_financial_report.report_aged_partner_balance_filters">
<div class="act_as_table data_table" style="width: 1140px !important;">
<div class="act_as_row labels">
<div class="act_as_cell">Date at filter</div>
<div class="act_as_cell">Target moves filter</div>
</div>
<div class="act_as_row">
<div class="act_as_cell">
<span t-field="o.date_at"/>
</div>
<div class="act_as_cell">
<t t-if="o.only_posted_moves">All posted entries</t>
<t t-if="not o.only_posted_moves">All entries</t>
</div>
</div>
</div>
</template>
<template id="account_financial_report.report_aged_partner_balance_lines_header">
<!-- Display table headers for lines -->
<div class="act_as_thead">
<div class="act_as_row labels">
<!--## partner-->
<div class="act_as_cell" style="width: 370px;">Partner</div>
<!--## amount_residual-->
<div class="act_as_cell" style="width: 110px;">Residual</div>
<!--## current-->
<div class="act_as_cell" style="width: 110px;">Current</div>
<!--## age_30_days-->
<div class="act_as_cell" style="width: 110px;">Age ≤ 30 d.</div>
<!--## age_60_days-->
<div class="act_as_cell" style="width: 110px;">Age ≤ 60 d.</div>
<!--## age_90_days-->
<div class="act_as_cell" style="width: 110px;">Age ≤ 90 d.</div>
<!--## age_120_days-->
<div class="act_as_cell" style="width: 110px;">Age ≤ 120 d.</div>
<!--## older-->
<div class="act_as_cell" style="width: 110px;">Older</div>
</div>
</div>
</template>
<template id="account_financial_report.report_aged_partner_balance_lines">
<!-- Display each lines -->
<t t-foreach="partner.line_ids" t-as="line">
<!-- # lines -->
<div class="act_as_row lines">
<!--## partner-->
<div class="act_as_cell left">
<span t-field="line.partner"/>
</div>
<!--## amount_residual-->
<div class="act_as_cell amount">
<span t-field="line.amount_residual"/>
</div>
<!--## current-->
<div class="act_as_cell amount">
<span t-field="line.current"/>
</div>
<!--## age_30_days-->
<div class="act_as_cell amount">
<span t-field="line.age_30_days"/>
</div>
<!--## age_60_days-->
<div class="act_as_cell amount">
<span t-field="line.age_60_days"/>
</div>
<!--## age_90_days-->
<div class="act_as_cell amount">
<span t-field="line.age_90_days"/>
</div>
<!--## age_120_days-->
<div class="act_as_cell amount">
<span t-field="line.age_120_days"/>
</div>
<!--## older-->
<div class="act_as_cell amount">
<span t-field="line.older"/>
</div>
</div>
</t>
</template>
<template id="account_financial_report.report_aged_partner_balance_move_lines">
<div class="act_as_table data_table" style="width: 1140px !important;">
<!-- Display table headers for move lines -->
<div class="act_as_thead">
<div class="act_as_row labels">
<!--## date-->
<div class="act_as_cell first_column" style="width: 60px;">Date</div>
<!--## move-->
<div class="act_as_cell" style="width: 100px;">Entry</div>
<!--## journal-->
<div class="act_as_cell" style="width: 40px;">Journal</div>
<!--## account code-->
<div class="act_as_cell" style="width: 50px;">Account</div>
<!--## partner-->
<div class="act_as_cell" style="width: 120px;">Partner</div>
<!--## ref - label-->
<div class="act_as_cell" style="width: 220px;">Ref - Label</div>
<!--## date_due-->
<div class="act_as_cell" style="width: 60px;">Due date</div>
<!--## amount_residual-->
<div class="act_as_cell" style="width: 70px;">Residual</div>
<!--## current-->
<div class="act_as_cell" style="width: 70px;">Current</div>
<!--## age_30_days-->
<div class="act_as_cell" style="width: 70px;">Age ≤ 30 d.</div>
<!--## age_60_days-->
<div class="act_as_cell" style="width: 70px;">Age ≤ 60 d.</div>
<!--## age_90_days-->
<div class="act_as_cell" style="width: 70px;">Age ≤ 90 d.</div>
<!--## age_120_days-->
<div class="act_as_cell" style="width: 70px;">Age ≤ 120 d.</div>
<!--## older-->
<div class="act_as_cell" style="width: 70px;">Older</div>
</div>
</div>
<!-- Display each move lines -->
<t t-foreach="partner.move_line_ids" t-as="line">
<!-- # lines or centralized lines -->
<div class="act_as_row lines">
<!--## date-->
<div class="act_as_cell left">
<span t-field="line.date"/>
</div>
<!--## move-->
<div class="act_as_cell left">
<span t-field="line.entry"/>
</div>
<!--## journal-->
<div class="act_as_cell left">
<span t-field="line.journal"/>
</div>
<!--## account code-->
<div class="act_as_cell left">
<span t-field="line.account"/>
</div>
<!--## partner-->
<div class="act_as_cell left">
<span t-field="line.partner"/>
</div>
<!--## ref - label-->
<div class="act_as_cell left">
<span t-field="line.label"/>
</div>
<!--## date_due-->
<div class="act_as_cell left">
<span t-field="line.date_due"/>
</div>
<!--## amount_residual-->
<div class="act_as_cell amount">
<span t-field="line.amount_residual"/>
</div>
<!--## current-->
<div class="act_as_cell amount">
<span t-field="line.current"/>
</div>
<!--## age_30_days-->
<div class="act_as_cell amount">
<span t-field="line.age_30_days"/>
</div>
<!--## age_60_days-->
<div class="act_as_cell amount">
<span t-field="line.age_60_days"/>
</div>
<!--## age_90_days-->
<div class="act_as_cell amount">
<span t-field="line.age_90_days"/>
</div>
<!--## age_120_days-->
<div class="act_as_cell amount">
<span t-field="line.age_120_days"/>
</div>
<!--## older-->
<div class="act_as_cell amount">
<span t-field="line.older"/>
</div>
</div>
</t>
</div>
</template>
<template id="account_financial_report.report_aged_partner_balance_partner_ending_cumul">
<!-- Display ending balance line for partner -->
<div class="act_as_table list_table" style="width: 1141px !important;">
<div class="act_as_row labels" style="font-weight: bold;">
<!--## date-->
<div class="act_as_cell right" style="width: 590px;">Partner cumul aged balance</div>
<!--## date_due-->
<div class="act_as_cell" style="width: 60px;"/>
<!--## amount_residual-->
<div class="act_as_cell amount" style="width: 70px;">
<span t-field="partner_cumul_line.amount_residual"/>
</div>
<!--## current-->
<div class="act_as_cell amount" style="width: 70px;">
<span t-field="partner_cumul_line.current"/>
</div>
<!--## age_30_days-->
<div class="act_as_cell amount" style="width: 70px;">
<span t-field="partner_cumul_line.age_30_days"/>
</div>
<!--## age_60_days-->
<div class="act_as_cell amount" style="width: 70px;">
<span t-field="partner_cumul_line.age_60_days"/>
</div>
<!--## age_90_days-->
<div class="act_as_cell amount" style="width: 70px;">
<span t-field="partner_cumul_line.age_90_days"/>
</div>
<!--## age_120_days-->
<div class="act_as_cell amount" style="width: 70px;">
<span t-field="partner_cumul_line.age_120_days"/>
</div>
<!--## older-->
<div class="act_as_cell amount" style="width: 70px;">
<span t-field="partner_cumul_line.older"/>
</div>
</div>
</div>
</template>
<template id="account_financial_report.report_aged_partner_balance_account_ending_cumul">
<!-- Display ending balance line for account -->
<div class="act_as_table list_table" style="width: 1141px !important;">
<div class="act_as_row labels" style="font-weight: bold;">
<t t-if="not show_move_line_details">
<!--## total-->
<div class="act_as_cell right" style="width: 370px;">Total</div>
<!--## amount_residual-->
<div class="act_as_cell amount" style="width: 110px;">
<span t-field="account.cumul_amount_residual"/>
</div>
<!--## current-->
<div class="act_as_cell amount" style="width: 110px;">
<span t-field="account.cumul_current"/>
</div>
<!--## age_30_days-->
<div class="act_as_cell amount" style="width: 110px;">
<span t-field="account.cumul_age_30_days"/>
</div>
<!--## age_60_days-->
<div class="act_as_cell amount" style="width: 110px;">
<span t-field="account.cumul_age_60_days"/>
</div>
<!--## age_90_days-->
<div class="act_as_cell amount" style="width: 110px;">
<span t-field="account.cumul_age_90_days"/>
</div>
<!--## age_120_days-->
<div class="act_as_cell amount" style="width: 110px;">
<span t-field="account.cumul_age_120_days"/>
</div>
<!--## older-->
<div class="act_as_cell amount" style="width: 110px;">
<span t-field="account.cumul_older"/>
</div>
</t>
<t t-if="show_move_line_details">
<!--## total-->
<div class="act_as_cell right" style="width: 590px;">Total</div>
<!--## date_due-->
<div class="act_as_cell" style="width: 60px;"/>
<!--## amount_residual-->
<div class="act_as_cell amount" style="width: 70px;">
<span t-field="account.cumul_amount_residual"/>
</div>
<!--## current-->
<div class="act_as_cell amount" style="width: 70px;">
<span t-field="account.cumul_current"/>
</div>
<!--## age_30_days-->
<div class="act_as_cell amount" style="width: 70px;">
<span t-field="account.cumul_age_30_days"/>
</div>
<!--## age_60_days-->
<div class="act_as_cell amount" style="width: 70px;">
<span t-field="account.cumul_age_60_days"/>
</div>
<!--## age_90_days-->
<div class="act_as_cell amount" style="width: 70px;">
<span t-field="account.cumul_age_90_days"/>
</div>
<!--## age_120_days-->
<div class="act_as_cell amount" style="width: 70px;">
<span t-field="account.cumul_age_120_days"/>
</div>
<!--## older-->
<div class="act_as_cell amount" style="width: 70px;">
<span t-field="account.cumul_older"/>
</div>
</t>
</div>
<div class="act_as_row" style="font-weight: bold; font-style: italic;">
<t t-if="not show_move_line_details">
<!--## total-->
<div class="act_as_cell right" style="width: 370px;">Percents</div>
<!--## amount_residual-->
<div class="act_as_cell amount" style="width: 110px;"/>
<!--## current-->
<div class="act_as_cell amount" style="width: 110px;"><span t-field="account.percent_current"/>%
</div>
<!--## age_30_days-->
<div class="act_as_cell amount" style="width: 110px;"><span t-field="account.percent_age_30_days"/>%
</div>
<!--## age_60_days-->
<div class="act_as_cell amount" style="width: 110px;"><span t-field="account.percent_age_60_days"/>%
</div>
<!--## age_90_days-->
<div class="act_as_cell amount" style="width: 110px;"><span t-field="account.percent_age_90_days"/>%
</div>
<!--## age_120_days-->
<div class="act_as_cell amount" style="width: 110px;"><span t-field="account.percent_age_120_days"/>
%
</div>
<!--## older-->
<div class="act_as_cell amount" style="width: 110px;"><span t-field="account.percent_older"/>%
</div>
</t>
<t t-if="show_move_line_details">
<!--## total-->
<div class="act_as_cell right" style="width: 590px;">Percents</div>
<!--## date_due-->
<div class="act_as_cell" style="width: 60px;"/>
<!--## amount_residual-->
<div class="act_as_cell amount" style="width: 70px;"/>
<!--## current-->
<div class="act_as_cell amount" style="width: 70px;"><span t-field="account.percent_current"/>%
</div>
<!--## age_30_days-->
<div class="act_as_cell amount" style="width: 70px;"><span t-field="account.percent_age_30_days"/>%
</div>
<!--## age_60_days-->
<div class="act_as_cell amount" style="width: 70px;"><span t-field="account.percent_age_60_days"/>%
</div>
<!--## age_90_days-->
<div class="act_as_cell amount" style="width: 70px;"><span t-field="account.percent_age_90_days"/>%
</div>
<!--## age_120_days-->
<div class="act_as_cell amount" style="width: 70px;"><span t-field="account.percent_age_120_days"/>%
</div>
<!--## older-->
<div class="act_as_cell amount" style="width: 70px;"><span t-field="account.percent_older"/>%
</div>
</t>
</div>
</div>
</template>
</odoo>

263
account_financial_report/report/templates/general_ledger.xml

@ -0,0 +1,263 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="report_general_ledger_qweb">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="o">
<t t-call="account_financial_report.internal_layout">
<t t-call="account_financial_report.report_general_ledger_base"/>
</t>
</t>
</t>
</template>
<template id="report_general_ledger_base">
<!-- Saved flag fields into variables, used to define columns display -->
<t t-set="show_cost_center" t-value="o.show_cost_center"/>
<t t-set="has_second_currency" t-value="o.has_second_currency"/>
<!-- Defines global variables used by internal layout -->
<t t-set="title">General Ledger</t>
<t t-set="company_name" t-value="o.company_id.name"/>
<div class="page">
<!-- Display filters -->
<t t-call="account_financial_report.report_general_ledger_filters"/>
<t t-foreach="o.account_ids" t-as="account">
<div class="page_break">
<!-- Display account header -->
<div class="act_as_table list_table" style="margin-top: 10px;"/>
<div class="act_as_caption account_title" style="width: 1141px !important;">
<span t-field="account.code"/> - <span t-field="account.name"/>
</div>
<t t-if="not account.partner_ids">
<!-- Display account move lines without partner regroup -->
<t t-call="account_financial_report.report_general_ledger_lines">
<t t-set="account_or_partner_object" t-value="account"/>
</t>
</t>
<t t-if="account.partner_ids">
<!-- Display account partners -->
<t t-foreach="account.partner_ids" t-as="partner">
<div class="page_break">
<!-- Display partner header -->
<div class="act_as_caption account_title">
<span t-field="partner.name"/>
</div>
<!-- Display partner move lines -->
<t t-call="account_financial_report.report_general_ledger_lines">
<t t-set="account_or_partner_object" t-value="partner"/>
</t>
<!-- Display partner footer -->
<t t-call="account_financial_report.report_general_ledger_ending_cumul">
<t t-set="account_or_partner_object" t-value="partner"/>
<t t-set="type" t-value='"partner_type"'/>
</t>
</div>
</t>
</t>
<!-- Display account footer -->
<t t-call="account_financial_report.report_general_ledger_ending_cumul">
<t t-set="account_or_partner_object" t-value="account"/>
<t t-set="type" t-value='"account_type"'/>
</t>
</div>
</t>
</div>
</template>
<template id="account_financial_report.report_general_ledger_filters">
<div class="act_as_table data_table" style="width: 1140px !important;">
<div class="act_as_row labels">
<div class="act_as_cell">Date range filter</div>
<div class="act_as_cell">Target moves filter</div>
<div class="act_as_cell">Account balance at 0 filter</div>
<div class="act_as_cell">Centralize filter</div>
</div>
<div class="act_as_row">
<div class="act_as_cell">
From: <span t-field="o.date_from"/> To: <span t-field="o.date_to"/>
</div>
<div class="act_as_cell">
<t t-if="o.only_posted_moves">All posted entries</t>
<t t-if="not o.only_posted_moves">All entries</t>
</div>
<div class="act_as_cell">
<t t-if="o.hide_account_balance_at_0">Hide</t>
<t t-if="not o.hide_account_balance_at_0">Show</t>
</div>
<div class="act_as_cell">
<t t-if="o.centralize">Yes</t>
<t t-if="not o.centralize">No</t>
</div>
</div>
</div>
</template>
<template id="account_financial_report.report_general_ledger_lines">
<div class="act_as_table data_table" style="width: 1140px !important;">
<!-- Display table headers for lines -->
<div class="act_as_thead">
<div class="act_as_row labels">
<!--## date-->
<div class="act_as_cell first_column" style="width: 60px;">Date</div>
<!--## move-->
<div class="act_as_cell" style="width: 100px;">Entry</div>
<!--## journal-->
<div class="act_as_cell" style="width: 40px;">Journal</div>
<!--## account code-->
<div class="act_as_cell" style="width: 50px;">Account</div>
<!--## partner-->
<div class="act_as_cell" style="width: 140px;">Partner</div>
<!--## ref - label-->
<div class="act_as_cell" style="width: 290px;">Ref - Label</div>
<t t-if="show_cost_center">
<!--## cost_center-->
<div class="act_as_cell" style="width: 100px;">Cost center</div>
</t>
<!--## matching_number-->
<div class="act_as_cell" style="width: 25px;">Rec.</div>
<!--## debit-->
<div class="act_as_cell amount" style="width: 75px;">Debit</div>
<!--## credit-->
<div class="act_as_cell amount" style="width: 75px;">Credit</div>
<!--## balance cumulated-->
<div class="act_as_cell amount" style="width: 75px;">Cumul. Bal.</div>
<!--## currency_name-->
<div class="act_as_cell" style="width: 35px;">Cur.</div>
<!--## amount_currency-->
<div class="act_as_cell amount" style="width: 75px;">Amount cur.</div>
</div>
</div>
<!-- Display first line with initial balance -->
<div class="act_as_row lines">
<!--## date-->
<div class="act_as_cell"/>
<!--## move-->
<div class="act_as_cell"/>
<!--## journal-->
<div class="act_as_cell"/>
<!--## account code-->
<div class="act_as_cell"/>
<!--## partner-->
<div class="act_as_cell"/>
<!--## ref - label-->
<div class="act_as_cell amount">Initial balance</div>
<t t-if="show_cost_center">
<!--## cost_center-->
<div class="act_as_cell"/>
</t>
<!--## matching_number-->
<div class="act_as_cell"/>
<!--## debit-->
<div class="act_as_cell amount"><span t-field="account_or_partner_object.initial_debit"/></div>
<!--## credit-->
<div class="act_as_cell amount"><span t-field="account_or_partner_object.initial_credit"/></div>
<!--## balance cumulated-->
<div class="act_as_cell amount"><span t-field="account_or_partner_object.initial_balance"/></div>
<!--## currency_name-->
<div class="act_as_cell"/>
<!--## amount_currency-->
<div class="act_as_cell"/>
</div>
<!-- Display each lines -->
<t t-foreach="account_or_partner_object.move_line_ids" t-as="line">
<!-- # lines or centralized lines -->
<div class="act_as_row lines">
<!--## date-->
<div class="act_as_cell left"><span t-field="line.date"/></div>
<!--## move-->
<div class="act_as_cell left">
<t t-set="res_model" t-value="'account.move'"/>
<span>
<a t-att-data-active-id="line.move_line_id.move_id.id"
t-att-data-res-model="res_model"
class="o_account_financial_reports_web_action"
style="color: black;">
<t t-raw="line.entry"/></a>
</span>
</div>
<!--## journal-->
<div class="act_as_cell left"><span t-field="line.journal"/></div>
<!--## account code-->
<div class="act_as_cell left"><span t-field="line.account"/></div>
<!--## partner-->
<div class="act_as_cell left">
<t t-set="res_model" t-value="'res.partner'"/>
<span t-if="line.partner">
<a t-att-data-active-id="line.move_line_id.partner_id.id"
t-att-data-res-model="res_model"
class="o_account_financial_reports_web_action"
style="color: black;"><t t-raw="line.partner"/></a>
</span>
</div>
<!--## ref - label-->
<div class="act_as_cell left"><span t-field="line.label"/></div>
<t t-if="show_cost_center">
<!--## cost_center-->
<div class="act_as_cell left"><span t-field="line.cost_center"/></div>
</t>
<!--## matching_number-->
<div class="act_as_cell"><span t-field="line.matching_number"/></div>
<!--## debit-->
<div class="act_as_cell amount"><span t-field="line.debit"/></div>
<!--## credit-->
<div class="act_as_cell amount"><span t-field="line.credit"/></div>
<!--## balance cumulated-->
<div class="act_as_cell amount"><span t-field="line.cumul_balance"/></div>
<!--## currency_name-->
<div class="act_as_cell"><span t-field="line.currency_name"/></div>
<t t-if="line.currency_name">
<!--## amount_currency-->
<div class="act_as_cell amount"><span t-field="line.amount_currency"/></div>
</t>
<t t-if="not line.currency_name">
<!--## amount_currency-->
<div class="act_as_cell"/>
</t>
</div>
</t>
</div>
</template>
<template id="account_financial_report.report_general_ledger_ending_cumul">
<!-- Display ending balance line for account or partner -->
<div class="act_as_table list_table" style="width: 1141px !important;">
<div class="act_as_row labels" style="font-weight: bold;">
<!--## date-->
<t t-if='type == "account_type"'>
<div class="act_as_cell first_column" style="width: 380px;"><span t-field="account_or_partner_object.code"/> - <span t-field="account_or_partner_object.name"/></div>
<div class="act_as_cell right" style="width: 290px;">Ending balance</div>
</t>
<t t-if='type == "partner_type"'>
<div class="act_as_cell first_column" style="width: 380px;"/>
<div class="act_as_cell right" style="width: 290px;">Partner ending balance</div>
</t>
<t t-if="show_cost_center">
<!--## cost_center-->
<div class="act_as_cell" style="width: 100px;"/>
</t>
<!--## matching_number-->
<div class="act_as_cell" style="width: 25px;"/>
<!--## debit-->
<div class="act_as_cell amount" style="width: 75px;"><span t-field="account_or_partner_object.final_debit"/></div>
<!--## credit-->
<div class="act_as_cell amount" style="width: 75px;"><span t-field="account_or_partner_object.final_credit"/></div>
<!--## balance cumulated-->
<div class="act_as_cell amount" style="width: 75px; padding-right: 1px;"><span t-field="account_or_partner_object.final_balance"/></div>
<!--## currency_name + amount_currency-->
<div class="act_as_cell" style="width: 110px;"/>
</div>
</div>
</template>
</odoo>

36
account_financial_report/report/templates/layouts.xml

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="account_financial_report.internal_layout">
<div class="header">
<div class="row">
<div class="col-xs-6">
<span t-esc="title"/>
</div>
<div class="col-xs-6 text-right">
<span t-esc="company_name"/>
</div>
</div>
</div>
<div class="article">
<link href="/account_financial_report/static/src/css/report.css" rel="stylesheet"/>
<t t-raw="0" />
</div>
<div class="footer">
<div class="row">
<div class="col-xs-6 custom_footer">
<span t-esc="context_timestamp(datetime.datetime.now()).strftime('%Y-%m-%d %H:%M')"/>
</div>
<div class="col-xs-6 text-right custom_footer">
<ul class="list-inline">
<li><span class="page"/></li>
<li>/</li>
<li><span class="topage"/></li>
</ul>
</div>
</div>
</div>
</template>
</odoo>

233
account_financial_report/report/templates/open_items.xml

@ -0,0 +1,233 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="account_financial_report.report_open_items_qweb">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="o">
<t t-call="account_financial_report.internal_layout">
<t t-call="account_financial_report.report_open_items_base"/>
</t>
</t>
</t>
</template>
<template id="account_financial_report.report_open_items_base">
<!-- Saved flag fields into variables, used to define columns display -->
<t t-set="has_second_currency" t-value="o.has_second_currency"/>
<!-- Defines global variables used by internal layout -->
<t t-set="title">Open Items</t>
<t t-set="company_name" t-value="o.company_id.name"/>
<div class="page">
<!-- Display filters -->
<t t-call="account_financial_report.report_open_items_filters"/>
<t t-foreach="o.account_ids" t-as="account">
<div class="page_break">
<!-- Display account header -->
<div class="act_as_table list_table" style="margin-top: 10px;"/>
<div class="act_as_caption account_title" style="width: 1141px !important;">
<span t-field="account.code"/>
-
<span t-field="account.name"/>
</div>
<!-- Display account partners -->
<t t-foreach="account.partner_ids" t-as="partner">
<div class="page_break">
<!-- Display partner header -->
<div class="act_as_caption account_title">
<span t-field="partner.name"/>
</div>
<!-- Display partner move lines -->
<t t-call="account_financial_report.report_open_items_lines"/>
<!-- Display partner footer -->
<t t-call="account_financial_report.report_open_items_ending_cumul">
<t t-set="account_or_partner_object" t-value="partner"/>
<t t-set="type" t-value='"partner_type"'/>
</t>
</div>
</t>
<!-- Display account footer -->
<t t-call="account_financial_report.report_open_items_ending_cumul">
<t t-set="account_or_partner_object" t-value="account"/>
<t t-set="type" t-value='"account_type"'/>
</t>
</div>
</t>
</div>
</template>
<template id="account_financial_report.report_open_items_filters">
<div class="act_as_table data_table" style="width: 1140px !important;">
<div class="act_as_row labels">
<div class="act_as_cell">Date at filter</div>
<div class="act_as_cell">Target moves filter</div>
<div class="act_as_cell">Account balance at 0 filter</div>
</div>
<div class="act_as_row">
<div class="act_as_cell">
<span t-field="o.date_at"/>
</div>
<div class="act_as_cell">
<t t-if="o.only_posted_moves">All posted entries</t>
<t t-if="not o.only_posted_moves">All entries</t>
</div>
<div class="act_as_cell">
<t t-if="o.hide_account_balance_at_0">Hide</t>
<t t-if="not o.hide_account_balance_at_0">Show</t>
</div>
</div>
</div>
</template>
<template id="account_financial_report.report_open_items_lines">
<div class="act_as_table data_table" style="width: 1140px !important;">
<!-- Display table headers for lines -->
<div class="act_as_thead">
<div class="act_as_row labels">
<!--## date-->
<div class="act_as_cell first_column" style="width: 60px;">Date</div>
<!--## move-->
<div class="act_as_cell" style="width: 100px;">Entry</div>
<!--## journal-->
<div class="act_as_cell" style="width: 40px;">Journal</div>
<!--## account code-->
<div class="act_as_cell" style="width: 50px;">Account</div>
<!--## partner-->
<div class="act_as_cell" style="width: 140px;">Partner</div>
<!--## ref - label-->
<div class="act_as_cell" style="width: 290px;">Ref - Label</div>
<!--## date_due-->
<div class="act_as_cell" style="width: 60px;">Due date</div>
<!--## amount_total_due-->
<div class="act_as_cell" style="width: 75px;">Original</div>
<!--## amount_residual-->
<div class="act_as_cell" style="width: 75px;">Residual</div>
<!--## currency_name-->
<div class="act_as_cell" style="width: 35px;">Cur.</div>
<!--## amount_total_due_currency-->
<div class="act_as_cell amount" style="width: 75px;">Cur. Original</div>
<!--## amount_residual_currency-->
<div class="act_as_cell amount" style="width: 75px;">Cur. Residual</div>
</div>
</div>
<!-- Display each lines -->
<t t-foreach="partner.move_line_ids" t-as="line">
<!-- # lines or centralized lines -->
<div class="act_as_row lines">
<!--## date-->
<div class="act_as_cell left">
<span t-field="line.date"/>
</div>
<!--## move-->
<div class="act_as_cell left">
<t t-set="res_model" t-value="'account.move'"/>
<span>
<a t-att-data-active-id="line.move_line_id.move_id.id"
t-att-data-res-model="res_model"
class="o_account_financial_reports_web_action"
style="color: black;">
<t t-raw="line.entry"/>
</a>
</span>
</div>
<!--## journal-->
<div class="act_as_cell left">
<span t-field="line.journal"/>
</div>
<!--## account code-->
<div class="act_as_cell left">
<span t-field="line.account"/>
</div>
<!--## partner-->
<div class="act_as_cell left">
<t t-set="res_model" t-value="'res.partner'"/>
<span t-if="line.partner">
<a t-att-data-active-id="line.move_line_id.partner_id.id"
t-att-data-res-model="res_model"
class="o_account_financial_reports_web_action"
style="color: black;">
<t t-raw="line.partner"/>
</a>
</span>
</div>
<!--## ref - label-->
<div class="act_as_cell left">
<span t-field="line.label"/>
</div>
<!--## date_due-->
<div class="act_as_cell left">
<span t-field="line.date_due"/>
</div>
<!--## amount_total_due-->
<div class="act_as_cell amount">
<span t-field="line.amount_total_due"/>
</div>
<!--## amount_residual-->
<div class="act_as_cell amount">
<span t-field="line.amount_residual"/>
</div>
<!--## currency_name-->
<div class="act_as_cell">
<span t-field="line.currency_name"/>
</div>
<t t-if="line.currency_name">
<!--## amount_total_due_currency-->
<div class="act_as_cell amount">
<span t-field="line.amount_total_due_currency"/>
</div>
<!--## amount_residual_currency-->
<div class="act_as_cell amount">
<span t-field="line.amount_residual_currency"/>
</div>
</t>
<t t-if="not line.currency_name">
<!--## amount_total_due_currency-->
<div class="act_as_cell"/>
<!--## amount_residual_currency-->
<div class="act_as_cell"/>
</t>
</div>
</t>
</div>
</template>
<template id="account_financial_report.report_open_items_ending_cumul">
<!-- Display ending balance line for account or partner -->
<div class="act_as_table list_table" style="width: 1141px !important;">
<div class="act_as_row labels" style="font-weight: bold;">
<!--## date-->
<t t-if='type == "account_type"'>
<div class="act_as_cell first_column" style="width: 380px;">
<span t-field="account_or_partner_object.code"/>
-
<span t-field="account_or_partner_object.name"/>
</div>
<div class="act_as_cell right" style="width: 290px;">Ending balance</div>
</t>
<t t-if='type == "partner_type"'>
<div class="act_as_cell first_column" style="width: 380px;"/>
<div class="act_as_cell right" style="width: 290px;">Partner ending balance</div>
</t>
<!--## date_due-->
<div class="act_as_cell" style="width: 60px;"/>
<!--## amount_total_due-->
<div class="act_as_cell amount" style="width: 75px;"/>
<!--## amount_currency-->
<div class="act_as_cell amount" style="width: 75px;">
<span t-field="account_or_partner_object.final_amount_residual"/>
</div>
<!--## currency_name + amount_total_due_currency + amount_residual_currency -->
<div class="act_as_cell" style="width: 185px;"/>
</div>
</div>
</template>
</odoo>

155
account_financial_report/report/templates/trial_balance.xml

@ -0,0 +1,155 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="account_financial_report.report_trial_balance_qweb">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="o">
<t t-call="account_financial_report.internal_layout">
<t t-call="account_financial_report.report_trial_balance_base"/>
</t>
</t>
</t>
</template>
<template id="account_financial_report.report_trial_balance_base">
<!-- Saved flag fields into variables, used to define columns display -->
<t t-set="show_partner_details" t-value="o.show_partner_details"/>
<!-- Defines global variables used by internal layout -->
<t t-set="title">Trial Balance</t>
<t t-set="company_name" t-value="o.company_id.name"/>
<div class="page">
<!-- Display filters -->
<t t-call="account_financial_report.report_trial_balance_filters"/>
<div class="act_as_table list_table" style="margin-top: 10px;"/>
<!-- Display account lines -->
<t t-if="not show_partner_details">
<div class="act_as_table data_table" style="width: 1140px !important;">
<!-- Display account header -->
<t t-call="account_financial_report.report_trial_balance_lines_header"/>
<!-- Display each lines -->
<t t-foreach="o.account_ids" t-as="line">
<!-- Display account lines -->
<t t-call="account_financial_report.report_trial_balance_line"/>
</t>
</div>
</t>
<!-- Display partner lines -->
<t t-if="show_partner_details">
<t t-foreach="o.account_ids" t-as="account">
<div class="page_break">
<!-- Display account header -->
<div class="act_as_table list_table" style="margin-top: 10px;"/>
<div class="act_as_caption account_title" style="width: 1141px !important;">
<span t-field="account.code"/> - <span t-field="account.name"/>
</div>
<div class="act_as_table data_table" style="width: 1140px !important;">
<!-- Display account/partner header -->
<t t-call="account_financial_report.report_trial_balance_lines_header"/>
<!-- Display each partners -->
<t t-foreach="account.partner_ids" t-as="line">
<!-- Display partner line -->
<t t-call="account_financial_report.report_trial_balance_line"/>
</t>
</div>
<!-- Display account footer -->
<t t-call="account_financial_report.report_trial_balance_account_footer"/>
</div>
</t>
</t>
</div>
</template>
<template id="account_financial_report.report_trial_balance_filters">
<div class="act_as_table data_table" style="width: 1140px !important;">
<div class="act_as_row labels">
<div class="act_as_cell">Date range filter</div>
<div class="act_as_cell">Target moves filter</div>
<div class="act_as_cell">Account balance at 0 filter</div>
</div>
<div class="act_as_row">
<div class="act_as_cell">
From: <span t-field="o.date_from"/> To: <span t-field="o.date_to"/>
</div>
<div class="act_as_cell">
<t t-if="o.only_posted_moves">All posted entries</t>
<t t-if="not o.only_posted_moves">All entries</t>
</div>
<div class="act_as_cell">
<t t-if="o.hide_account_balance_at_0">Hide</t>
<t t-if="not o.hide_account_balance_at_0">Show</t>
</div>
</div>
</div>
</template>
<template id="account_financial_report.report_trial_balance_lines_header">
<!-- Display table headers for lines -->
<div class="act_as_thead">
<div class="act_as_row labels">
<t t-if="not show_partner_details">
<!--## Code-->
<div class="act_as_cell" style="width: 100px;">Code</div>
<!--## Account-->
<div class="act_as_cell" style="width: 600px;">Account</div>
</t>
<t t-if="show_partner_details">
<!--## Partner-->
<div class="act_as_cell" style="width: 700px;">Partner</div>
</t>
<!--## Initial balance-->
<div class="act_as_cell" style="width: 110px;">Initial balance</div>
<!--## Debit-->
<div class="act_as_cell" style="width: 110px;">Debit</div>
<!--## Credit-->
<div class="act_as_cell" style="width: 110px;">Credit</div>
<!--## Ending balance-->
<div class="act_as_cell" style="width: 110px;">Ending balance</div>
</div>
</div>
</template>
<template id="account_financial_report.report_trial_balance_line">
<!-- # line -->
<div class="act_as_row lines">
<t t-if="not show_partner_details">
<!--## Code-->
<div class="act_as_cell left"><span t-field="line.code"/></div>
</t>
<!--## Account/Partner-->
<div class="act_as_cell left"><span t-field="line.name"/></div>
<!--## Initial balance-->
<div class="act_as_cell amount"><span t-field="line.initial_balance"/></div>
<!--## Debit-->
<div class="act_as_cell amount"><span t-field="line.debit"/></div>
<!--## Credit-->
<div class="act_as_cell amount"><span t-field="line.credit"/></div>
<!--## Ending balance-->
<div class="act_as_cell amount"><span t-field="line.final_balance"/></div>
</div>
</template>
<template id="account_financial_report.report_trial_balance_account_footer">
<!-- Display account footer -->
<div class="act_as_table list_table" style="width: 1141px !important;">
<div class="act_as_row labels" style="font-weight: bold;">
<!--## Account-->
<div class="act_as_cell left" style="width: 700px;"><span t-field="account.code"/> - <span t-field="account.name"/></div>
<!--## Initial balance-->
<div class="act_as_cell amount" style="width: 110px;"><span t-field="account.initial_balance"/></div>
<!--## Debit-->
<div class="act_as_cell amount" style="width: 110px;"><span t-field="account.debit"/></div>
<!--## Credit-->
<div class="act_as_cell amount" style="width: 110px;"><span t-field="account.credit"/></div>
<!--## Ending balance-->
<div class="act_as_cell amount" style="width: 110px;"><span t-field="account.final_balance"/></div>
</div>
</div>
</template>
</odoo>

263
account_financial_report/report/trial_balance.py

@ -0,0 +1,263 @@
# © 2016 Julien Coux (Camptocamp)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import models, fields, api
class TrialBalanceReport(models.TransientModel):
""" Here, we just define class fields.
For methods, go more bottom at this file.
The class hierarchy is :
* TrialBalanceReport
** TrialBalanceReportAccount
*** TrialBalanceReportPartner
If "show_partner_details" is selected
"""
_name = 'report_trial_balance'
# Filters fields, used for data computation
date_from = fields.Date()
date_to = fields.Date()
fy_start_date = fields.Date()
only_posted_moves = fields.Boolean()
hide_account_balance_at_0 = fields.Boolean()
company_id = fields.Many2one(comodel_name='res.company')
filter_account_ids = fields.Many2many(comodel_name='account.account')
filter_partner_ids = fields.Many2many(comodel_name='res.partner')
show_partner_details = fields.Boolean()
# General Ledger Report Data fields,
# used as base for compute the data reports
general_ledger_id = fields.Many2one(
comodel_name='report_general_ledger'
)
# Data fields, used to browse report data
account_ids = fields.One2many(
comodel_name='report_trial_balance_account',
inverse_name='report_id'
)
class TrialBalanceReportAccount(models.TransientModel):
_name = 'report_trial_balance_account'
_order = 'code ASC'
report_id = fields.Many2one(
comodel_name='report_trial_balance',
ondelete='cascade',
index=True
)
# Data fields, used to keep link with real object
account_id = fields.Many2one(
'account.account',
index=True
)
# Data fields, used for report display
code = fields.Char()
name = fields.Char()
initial_balance = fields.Float(digits=(16, 2))
debit = fields.Float(digits=(16, 2))
credit = fields.Float(digits=(16, 2))
final_balance = fields.Float(digits=(16, 2))
# Data fields, used to browse report data
partner_ids = fields.One2many(
comodel_name='report_trial_balance_partner',
inverse_name='report_account_id'
)
class TrialBalanceReportPartner(models.TransientModel):
_name = 'report_trial_balance_partner'
report_account_id = fields.Many2one(
comodel_name='report_trial_balance_account',
ondelete='cascade',
index=True
)
# Data fields, used to keep link with real object
partner_id = fields.Many2one(
'res.partner',
index=True
)
# Data fields, used for report display
name = fields.Char()
initial_balance = fields.Float(digits=(16, 2))
debit = fields.Float(digits=(16, 2))
credit = fields.Float(digits=(16, 2))
final_balance = fields.Float(digits=(16, 2))
@api.model
def _generate_order_by(self, order_spec, query):
"""Custom order to display "No partner allocated" at last position."""
return """
ORDER BY
CASE
WHEN "report_trial_balance_partner"."partner_id" IS NOT NULL
THEN 0
ELSE 1
END,
"report_trial_balance_partner"."name"
"""
class TrialBalanceReportCompute(models.TransientModel):
""" Here, we just define methods.
For class fields, go more top at this file.
"""
_inherit = 'report_trial_balance'
@api.multi
def print_report(self, report_type):
self.ensure_one()
if report_type == 'xlsx':
report_name = 'a_f_r.report_trial_balance_xlsx'
else:
report_name = 'account_financial_report.' \
'report_trial_balance_qweb'
return self.env['ir.actions.report'].search(
[('report_name', '=', report_name),
('report_type', '=', report_type)], limit=1).report_action(self)
def _get_html(self):
result = {}
rcontext = {}
context = dict(self.env.context)
report = self.browse(context.get('active_id'))
if report:
rcontext['o'] = report
result['html'] = self.env.ref(
'account_financial_report.report_trial_balance').render(
rcontext)
return result
@api.model
def get_html(self, given_context=None):
return self._get_html()
def _prepare_report_general_ledger(self):
self.ensure_one()
return {
'date_from': self.date_from,
'date_to': self.date_to,
'only_posted_moves': self.only_posted_moves,
'hide_account_balance_at_0': self.hide_account_balance_at_0,
'company_id': self.company_id.id,
'filter_account_ids': [(6, 0, self.filter_account_ids.ids)],
'filter_partner_ids': [(6, 0, self.filter_partner_ids.ids)],
'fy_start_date': self.fy_start_date,
}
@api.multi
def compute_data_for_report(self):
self.ensure_one()
# Compute General Ledger Report Data.
# The data of Trial Balance Report
# are based on General Ledger Report data.
model = self.env['report_general_ledger']
self.general_ledger_id = model.create(
self._prepare_report_general_ledger()
)
self.general_ledger_id.compute_data_for_report(
with_line_details=False, with_partners=self.show_partner_details
)
# Compute report data
self._inject_account_values()
if self.show_partner_details:
self._inject_partner_values()
# Refresh cache because all data are computed with SQL requests
self.refresh()
def _inject_account_values(self):
"""Inject report values for report_trial_balance_account"""
query_inject_account = """
INSERT INTO
report_trial_balance_account
(
report_id,
create_uid,
create_date,
account_id,
code,
name,
initial_balance,
debit,
credit,
final_balance
)
SELECT
%s AS report_id,
%s AS create_uid,
NOW() AS create_date,
rag.account_id,
rag.code,
rag.name,
rag.initial_balance AS initial_balance,
rag.final_debit - rag.initial_debit AS debit,
rag.final_credit - rag.initial_credit AS credit,
rag.final_balance AS final_balance
FROM
report_general_ledger_account rag
WHERE
rag.report_id = %s
"""
query_inject_account_params = (
self.id,
self.env.uid,
self.general_ledger_id.id,
)
self.env.cr.execute(query_inject_account, query_inject_account_params)
def _inject_partner_values(self):
"""Inject report values for report_trial_balance_partner"""
query_inject_partner = """
INSERT INTO
report_trial_balance_partner
(
report_account_id,
create_uid,
create_date,
partner_id,
name,
initial_balance,
debit,
credit,
final_balance
)
SELECT
ra.id AS report_account_id,
%s AS create_uid,
NOW() AS create_date,
rpg.partner_id,
rpg.name,
rpg.initial_balance AS initial_balance,
rpg.final_debit - rpg.initial_debit AS debit,
rpg.final_credit - rpg.initial_credit AS credit,
rpg.final_balance AS final_balance
FROM
report_general_ledger_partner rpg
INNER JOIN
report_general_ledger_account rag ON rpg.report_account_id = rag.id
INNER JOIN
report_trial_balance_account ra ON rag.code = ra.code
WHERE
rag.report_id = %s
AND ra.report_id = %s
"""
query_inject_partner_params = (
self.env.uid,
self.general_ledger_id.id,
self.id,
)
self.env.cr.execute(query_inject_partner, query_inject_partner_params)

122
account_financial_report/report/trial_balance_xlsx.py

@ -0,0 +1,122 @@
# Author: Julien Coux
# Copyright 2016 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import _, models
class TrialBalanceXslx(models.AbstractModel):
_name = 'report.a_f_r.report_trial_balance_xlsx'
_inherit = 'report.account_financial_report.abstract_report_xlsx'
def _get_report_name(self):
return _('Trial Balance')
def _get_report_columns(self, report):
if not report.show_partner_details:
return {
0: {'header': _('Code'), 'field': 'code', 'width': 10},
1: {'header': _('Account'), 'field': 'name', 'width': 60},
2: {'header': _('Initial balance'),
'field': 'initial_balance',
'type': 'amount',
'width': 14},
3: {'header': _('Debit'),
'field': 'debit',
'type': 'amount',
'width': 14},
4: {'header': _('Credit'),
'field': 'credit',
'type': 'amount',
'width': 14},
5: {'header': _('Ending balance'),
'field': 'final_balance',
'type': 'amount',
'width': 14},
}
else:
return {
0: {'header': _('Partner'), 'field': 'name', 'width': 70},
1: {'header': _('Initial balance'),
'field': 'initial_balance',
'type': 'amount',
'width': 14},
2: {'header': _('Debit'),
'field': 'debit',
'type': 'amount',
'width': 14},
3: {'header': _('Credit'),
'field': 'credit',
'type': 'amount',
'width': 14},
4: {'header': _('Ending balance'),
'field': 'final_balance',
'type': 'amount',
'width': 14},
}
def _get_report_filters(self, report):
return [
[_('Date range filter'),
_('From: %s To: %s') % (report.date_from, report.date_to)],
[_('Target moves filter'),
_('All posted entries') if report.only_posted_moves
else _('All entries')],
[_('Account balance at 0 filter'),
_('Hide') if report.hide_account_balance_at_0 else _('Show')],
]
def _get_col_count_filter_name(self):
return 2
def _get_col_count_filter_value(self):
return 3
def _generate_report_content(self, workbook, report):
if not report.show_partner_details:
# Display array header for account lines
self.write_array_header()
# For each account
for account in report.account_ids:
if not report.show_partner_details:
# Display account lines
self.write_line(account)
else:
# Write account title
self.write_array_title(account.code + ' - ' + account.name)
# Display array header for partner lines
self.write_array_header()
# For each partner
for partner in account.partner_ids:
# Display partner lines
self.write_line(partner)
# Display account footer line
self.write_account_footer(account,
account.code + ' - ' + account.name)
# Line break
self.row_pos += 2
def write_account_footer(self, account, name_value):
"""Specific function to write account footer for Trial Balance"""
for col_pos, column in self.columns.items():
if column['field'] == 'name':
value = name_value
else:
value = getattr(account, column['field'])
cell_type = column.get('type', 'string')
if cell_type == 'string':
self.sheet.write_string(self.row_pos, col_pos, value or '',
self.format_header_left)
elif cell_type == 'amount':
self.sheet.write_number(self.row_pos, col_pos, float(value),
self.format_header_amount)
self.row_pos += 1

151
account_financial_report/reports.xml

@ -0,0 +1,151 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- PDF/HMTL REPORTS -->
<!-- General Ledger -->
<report
id="action_report_general_ledger_qweb"
model="report_general_ledger"
string="General Ledger"
report_type="qweb-pdf"
name="account_financial_report.report_general_ledger_qweb"
file="account_financial_report.report_general_ledger"
/>
<report
id="action_report_general_ledger_html"
model="report_general_ledger"
string="General Ledger"
report_type="qweb-html"
name="account_financial_report.report_general_ledger_qweb"
file="account_financial_report.report_general_ledger"
/>
<!-- Trial Balance -->
<report
id="action_report_trial_balance_qweb"
model="report_trial_balance"
string="Trial Balance"
report_type="qweb-pdf"
name="account_financial_report.report_trial_balance_qweb"
file="account_financial_report.report_trial_balance_qweb"
/>
<report
id="action_report_trial_balance_html"
model="report_trial_balance"
string="Trial Balance"
report_type="qweb-html"
name="account_financial_report.report_trial_balance_qweb"
file="account_financial_report.report_trial_balance_html"
/>
<!-- Open Items -->
<report
id="action_report_open_items_qweb"
model="report_open_items"
string="Open Items"
report_type="qweb-pdf"
name="account_financial_report.report_open_items_qweb"
file="account_financial_report.report_open_items_qweb"
/>
<report
id="action_report_open_items_html"
model="report_open_items"
string="Open Items"
report_type="qweb-html"
name="account_financial_report.report_open_items_qweb"
file="account_financial_report.report_open_items_html"
/>
<!-- Aged Partner Balance -->
<report
id="action_report_aged_partner_balance_qweb"
model="report_aged_partner_balance"
string="Aged Partner Balance"
report_type="qweb-pdf"
name="account_financial_report.report_aged_partner_balance_qweb"
file="account_financial_report.report_aged_partner_balance_qweb"
/>
<report
id="action_report_aged_partner_balance_html"
model="report_aged_partner_balance"
string="Aged Partner Balance"
report_type="qweb-html"
name="account_financial_report.report_aged_partner_balance_qweb"
file="account_financial_report.report_aged_partner_balance_html"
/>
<!-- PDF REPORTS : paperformat -->
<record id="report_qweb_paperformat" model="report.paperformat">
<field name="name">Account financial report qweb paperformat</field>
<field name="default" eval="True"/>
<field name="format">custom</field>
<field name="page_height">297</field>
<field name="page_width">210</field>
<field name="orientation">Portrait</field>
<field name="margin_top">12</field>
<field name="margin_bottom">8</field>
<field name="margin_left">5</field>
<field name="margin_right">5</field>
<field name="header_line" eval="False"/>
<field name="header_spacing">10</field>
<field name="dpi">110</field>
</record>
<record id="action_report_general_ledger_qweb" model="ir.actions.report">
<field name="paperformat_id" ref="report_qweb_paperformat"/>
</record>
<record id="action_report_trial_balance_qweb" model="ir.actions.report">
<field name="paperformat_id" ref="report_qweb_paperformat"/>
</record>
<record id="action_report_open_items_qweb" model="ir.actions.report">
<field name="paperformat_id" ref="report_qweb_paperformat"/>
</record>
<record id="action_report_aged_partner_balance_qweb"
model="ir.actions.report">
<field name="paperformat_id" ref="report_qweb_paperformat"/>
</record>
<!-- XLSX REPORTS -->
<record id="action_report_general_ledger_xlsx" model="ir.actions.report">
<field name="name">General Ledger XLSX</field>
<field name="model">report_general_ledger</field>
<field name="type">ir.actions.report</field>
<field name="report_name">a_f_r.report_general_ledger_xlsx</field>
<field name="report_type">xlsx</field>
<field name="report_file">report_general_ledger</field>
</record>
<record id="action_report_trial_balance_xlsx" model="ir.actions.report">
<field name="name">Trial Balance XLSX</field>
<field name="model">report_trial_balance</field>
<field name="type">ir.actions.report</field>
<field name="report_name">a_f_r.report_trial_balance_xlsx</field>
<field name="report_type">xlsx</field>
<field name="report_file">report_trial_balance</field>
</record>
<record id="action_report_open_items_xlsx" model="ir.actions.report">
<field name="name">Open Items XLSX</field>
<field name="model">report_open_items</field>
<field name="type">ir.actions.report</field>
<field name="report_name">a_f_r.report_open_items_xlsx</field>
<field name="report_type">xlsx</field>
<field name="report_file">report_open_items</field>
</record>
<record id="action_report_aged_partner_balance_xlsx" model="ir.actions.report">
<field name="name">Aged Partner Balance XLSX</field>
<field name="model">report_aged_partner_balance</field>
<field name="type">ir.actions.report</field>
<field name="report_name">a_f_r.report_aged_partner_balance_xlsx</field>
<field name="report_type">xlsx</field>
<field name="report_file">report_aged_partner_balance</field>
</record>
</odoo>

BIN
account_financial_report/static/description/icon.png

After

Width: 256  |  Height: 256  |  Size: 15 KiB

105
account_financial_report/static/src/css/report.css

@ -0,0 +1,105 @@
body, table, td, span, div {
font-family: Helvetica, Arial;
}
.act_as_table {
display: table !important;
}
.act_as_row {
display: table-row !important;
page-break-inside: avoid;
}
.act_as_cell {
display: table-cell !important;
page-break-inside: avoid;
}
.act_as_thead {
display: table-header-group !important;
}
.act_as_tbody {
display: table-row-group !important;
}
.list_table, .data_table, .totals_table{
width: 100% !important;
table-layout: fixed !important;
}
.act_as_row.labels {
background-color:#F0F0F0 !important;
}
.list_table, .data_table, .totals_table, .list_table .act_as_row {
border-left:0px;
border-right:0px;
text-align:left;
font-size:10px;
padding-right:3px;
padding-left:3px;
padding-top:2px;
padding-bottom:2px;
border-collapse:collapse;
}
.totals_table {
font-weight: bold;
text-align: center;
}
.list_table .act_as_row.labels, .list_table .act_as_row.initial_balance, .list_table .act_as_row.lines {
border-color:grey !important;
border-bottom:1px solid lightGrey !important;
}
.data_table .act_as_cell{
border: 1px solid lightGrey;
text-align: center;
}
.data_table .act_as_cell, .list_table .act_as_cell, .totals_table .act_as_cell {
word-wrap: break-word;
}
.data_table .act_as_row.labels, .totals_table .act_as_row.labels {
font-weight: bold;
}
.initial_balance .act_as_cell {
font-style:italic;
}
.account_title {
font-size:11px;
font-weight:bold;
}
.account_title.labels {
background-color:#F0F0F0 !important;
}
.act_as_cell.amount {
word-wrap:normal;
text-align:right;
}
.act_as_cell.left {
text-align:left;
}
.act_as_cell.right {
text-align:right;
}
.list_table .act_as_cell{
padding-left: 5px;
/* border-right:1px solid lightGrey; uncomment to active column lines */
}
.list_table .act_as_cell.first_column {
padding-left: 0px;
/* border-left:1px solid lightGrey; uncomment to active column lines */
}
.overflow_ellipsis {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.custom_footer {
font-size:7px !important;
}
.page_break {
page-break-inside: avoid;
}
.button_row {
padding-bottom: 10px;
}
.o_account_financial_reports_page {
background-color: @odoo-view-background-color;
color: @odoo-main-text-color;
padding-top: 10px;
}

109
account_financial_report/static/src/js/account_financial_report_backend.js

@ -0,0 +1,109 @@
odoo.define('account_financial_report.account_financial_report_backend', function (require) {
'use strict';
var core = require('web.core');
var Widget = require('web.Widget');
var ControlPanelMixin = require('web.ControlPanelMixin');
var session = require('web.session');
var ReportWidget = require('account_financial_report.account_financial_report_widget');
var framework = require('web.framework');
var crash_manager = require('web.crash_manager');
var QWeb = core.qweb;
var report_backend = Widget.extend(ControlPanelMixin, {
// Stores all the parameters of the action.
events: {
'click .o_account_financial_reports_print': 'print',
'click .o_account_financial_reports_export': 'export',
},
init: function(parent, action) {
this.actionManager = parent;
this.given_context = {};
this.odoo_context = action.context;
this.controller_url = action.context.url;
if (action.context.context) {
this.given_context = action.context.context;
}
this.given_context.active_id = action.context.active_id || action.params.active_id;
this.given_context.model = action.context.active_model || false;
this.given_context.ttype = action.context.ttype || false;
return this._super.apply(this, arguments);
},
willStart: function() {
return $.when(this.get_html());
},
set_html: function() {
var self = this;
var def = $.when();
if (!this.report_widget) {
this.report_widget = new ReportWidget(this, this.given_context);
def = this.report_widget.appendTo(this.$el);
}
def.then(function () {
self.report_widget.$el.html(self.html);
});
},
start: function() {
this.set_html();
return this._super();
},
// Fetches the html and is previous report.context if any, else create it
get_html: function() {
var self = this;
var defs = [];
return this._rpc({
model: this.given_context.model,
method: 'get_html',
args: [self.given_context],
context: self.odoo_context,
})
.then(function (result) {
self.html = result.html;
defs.push(self.update_cp());
return $.when.apply($, defs);
});
},
// Updates the control panel and render the elements that have yet to be rendered
update_cp: function() {
if (!this.$buttons) {
}
var status = {
breadcrumbs: this.actionManager.get_breadcrumbs(),
cp_content: {$buttons: this.$buttons},
};
return this.update_control_panel(status);
},
do_show: function() {
this._super();
this.update_cp();
},
print: function(e) {
var self = this;
this._rpc({
model: this.given_context.model,
method: 'print_report',
args: [this.given_context.active_id, 'qweb-pdf'],
context: self.odoo_context,
}).then(function(result){
self.do_action(result);
});
},
export: function(e) {
var self = this;
this._rpc({
model: this.given_context.model,
method: 'print_report',
args: [this.given_context.active_id, 'xlsx'],
context: self.odoo_context,
})
.then(function(result){
self.do_action(result);
});
},
});
core.action_registry.add("account_financial_report_backend", report_backend);
return report_backend;
});

37
account_financial_report/static/src/js/account_financial_report_widgets.js

@ -0,0 +1,37 @@
odoo.define('account_financial_report.account_financial_report_widget', function
(require) {
'use strict';
var core = require('web.core');
var Widget = require('web.Widget');
var QWeb = core.qweb;
var _t = core._t;
var accountFinancialReportWidget = Widget.extend({
events: {
'click .o_account_financial_reports_web_action': 'boundLink',
},
init: function(parent) {
this._super.apply(this, arguments);
},
start: function() {
return this._super.apply(this, arguments);
},
boundLink: function(e) {
var res_model = $(e.target).data('res-model')
var res_id = $(e.target).data('active-id')
return this.do_action({
type: 'ir.actions.act_window',
res_model: res_model,
res_id: res_id,
views: [[false, 'form']],
target: 'current'
});
},
});
return accountFinancialReportWidget;
});

9
account_financial_report/tests/__init__.py

@ -0,0 +1,9 @@
# © 2016 Julien Coux (Camptocamp)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).-
from . import abstract_test
from . import test_aged_partner_balance
from . import test_general_ledger
from . import test_open_items
from . import test_trial_balance

246
account_financial_report/tests/abstract_test.py

@ -0,0 +1,246 @@
# Author: Julien Coux
# Copyright 2016 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo.tests.common import TransactionCase
import logging
_logger = logging.getLogger(__name__)
try:
from xlrd import open_workbook
except ImportError:
_logger.debug('Can not import xlsxwriter`.')
class AbstractTest(TransactionCase):
"""Common technical tests for all reports."""
def setUp(cls):
super(AbstractTest, cls).setUp()
cls.model = cls._getReportModel()
cls.qweb_report_name = cls._getQwebReportName()
cls.xlsx_report_name = cls._getXlsxReportName()
cls.xlsx_action_name = cls._getXlsxReportActionName()
cls.report_title = cls._getReportTitle()
cls.base_filters = cls._getBaseFilters()
cls.additional_filters = cls._getAdditionalFiltersToBeTested()
cls.report = cls.model.create(cls.base_filters)
cls.report.compute_data_for_report()
def test_01_generation_report_qweb(self):
"""Check if report PDF/HTML is correctly generated"""
# Check if returned report action is correct
report_type = 'qweb-pdf'
report_action = self.report.print_report(report_type)
self.assertDictContainsSubset(
{
'type': 'ir.actions.report',
'report_name': self.qweb_report_name,
'report_type': 'qweb-pdf',
},
report_action
)
# Check if report template is correct
report = self.env['ir.actions.report'].search(
[('report_name', '=', self.qweb_report_name),
('report_type', '=', report_type)], limit=1)
self.assertEqual(report.report_type, 'qweb-pdf')
rep = report.render(self.report.ids, {})
self.assertTrue(self.report_title.encode('utf8') in rep[0])
self.assertTrue(
self.report.account_ids[0].name.encode('utf8') in rep[0]
)
def test_02_generation_report_html(self):
"""Check if report HTML is correctly generated"""
# Check if returned report action is correct
report_type = 'qweb-html'
report_action = self.report.print_report(report_type)
self.assertDictContainsSubset(
{
'type': 'ir.actions.report',
'report_name': self.qweb_report_name,
'report_type': 'qweb-html',
},
report_action
)
# Check if report template is correct
report = self.env['ir.actions.report'].search(
[('report_name', '=', self.qweb_report_name),
('report_type', '=', report_type)], limit=1)
self.assertEqual(report.report_type, 'qweb-html')
rep = report.render(self.report.ids, {})
self.assertTrue(self.report_title.encode('utf8') in rep[0])
self.assertTrue(
self.report.account_ids[0].name.encode('utf8') in rep[0]
)
def test_03_generation_report_xlsx(self):
"""Check if report XLSX is correctly generated"""
report_object = self.env['ir.actions.report']
# Check if returned report action is correct
report_type = 'xlsx'
report_action = self.report.print_report(report_type)
self.assertDictContainsSubset(
{
'type': 'ir.actions.report',
'report_name': self.xlsx_report_name,
'report_type': 'xlsx',
},
report_action
)
report = report_object._get_report_from_name(self.xlsx_report_name)
self.assertEqual(report.report_type, 'xlsx')
report_xlsx = report.render(self.report.ids, {})
wb = open_workbook(file_contents=report_xlsx[0])
sheet = wb.sheet_by_index(0)
class_name = 'report.%s' % report.report_name
self.assertEqual(sheet.cell(0, 0).value,
self.env[class_name]._get_report_name())
def test_04_compute_data(self):
"""Check that the SQL queries work with all filters options"""
for filters in [{}] + self.additional_filters:
current_filter = self.base_filters.copy()
current_filter.update(filters)
report = self.model.create(current_filter)
report.compute_data_for_report()
self.assertGreaterEqual(len(report.account_ids), 1)
# Same filters with only one account
current_filter = self.base_filters.copy()
current_filter.update(filters)
current_filter.update({
'filter_account_ids':
[(6, 0, report.account_ids[0].account_id.ids)],
})
report2 = self.model.create(current_filter)
report2.compute_data_for_report()
self.assertEqual(len(report2.account_ids), 1)
self.assertEqual(report2.account_ids.name,
report.account_ids[0].name)
if self._partner_test_is_possible(filters):
# Same filters with only one partner
report_partner_ids = report.account_ids.mapped('partner_ids')
partner_ids = report_partner_ids.mapped('partner_id')
current_filter = self.base_filters.copy()
current_filter.update(filters)
current_filter.update({
'filter_partner_ids': [(6, 0, partner_ids[0].ids)],
})
report3 = self.model.create(current_filter)
report3.compute_data_for_report()
self.assertGreaterEqual(len(report3.account_ids), 1)
report_partner_ids3 = report3.account_ids.mapped('partner_ids')
partner_ids3 = report_partner_ids3.mapped('partner_id')
self.assertEqual(len(partner_ids3), 1)
self.assertEqual(
partner_ids3.name,
partner_ids[0].name
)
# Same filters with only one partner and one account
report_partner_ids = report3.account_ids.mapped('partner_ids')
report_account_id = report_partner_ids.filtered(
lambda p: p.partner_id
)[0].report_account_id
current_filter = self.base_filters.copy()
current_filter.update(filters)
current_filter.update({
'filter_account_ids':
[(6, 0, report_account_id.account_id.ids)],
'filter_partner_ids': [(6, 0, partner_ids[0].ids)],
})
report4 = self.model.create(current_filter)
report4.compute_data_for_report()
self.assertEqual(len(report4.account_ids), 1)
self.assertEqual(report4.account_ids.name,
report_account_id.account_id.name)
report_partner_ids4 = report4.account_ids.mapped('partner_ids')
partner_ids4 = report_partner_ids4.mapped('partner_id')
self.assertEqual(len(partner_ids4), 1)
self.assertEqual(
partner_ids4.name,
partner_ids[0].name
)
def _partner_test_is_possible(self, filters):
"""
:return:
a boolean to indicate if partner test is possible
with current filters
"""
return True
def _getReportModel(self):
"""
:return: the report model name
"""
raise NotImplementedError()
def _getQwebReportName(self):
"""
:return: the qweb report name
"""
raise NotImplementedError()
def _getXlsxReportName(self):
"""
:return: the xlsx report name
"""
raise NotImplementedError()
def _getXlsxReportActionName(self):
"""
:return: the xlsx report action name
"""
raise NotImplementedError()
def _getReportTitle(self):
"""
:return: the report title displayed into the report
"""
raise NotImplementedError()
def _getBaseFilters(self):
"""
:return: the minimum required filters to generate report
"""
raise NotImplementedError()
def _getAdditionalFiltersToBeTested(self):
"""
:return: the additional filters to generate report variants
"""
raise NotImplementedError()

42
account_financial_report/tests/test_aged_partner_balance.py

@ -0,0 +1,42 @@
# Author: Julien Coux
# Copyright 2016 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import time
from . import abstract_test
class TestAgedPartnerBalance(abstract_test.AbstractTest):
"""
Technical tests for Aged Partner Balance Report.
"""
def _getReportModel(self):
return self.env['report_aged_partner_balance']
def _getQwebReportName(self):
return 'account_financial_report.report_aged_partner_balance_qweb'
def _getXlsxReportName(self):
return 'a_f_r.report_aged_partner_balance_xlsx'
def _getXlsxReportActionName(self):
return 'account_financial_report.' \
'action_report_aged_partner_balance_xlsx'
def _getReportTitle(self):
return 'Odoo Report'
def _getBaseFilters(self):
return {
'date_at': time.strftime('%Y-12-31'),
'company_id': self.env.ref('base.main_company').id,
}
def _getAdditionalFiltersToBeTested(self):
return [
{'only_posted_moves': True},
{'show_move_line_details': True},
{'only_posted_moves': True, 'show_move_line_details': True},
]

466
account_financial_report/tests/test_general_ledger.py

@ -0,0 +1,466 @@
# Author: Julien Coux
# Copyright 2016 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import time
from . import abstract_test
from odoo.tests.common import TransactionCase
class TestGeneralLedger(abstract_test.AbstractTest):
"""
Technical tests for General Ledger Report.
"""
def _getReportModel(self):
return self.env['report_general_ledger']
def _getQwebReportName(self):
return 'account_financial_report.report_general_ledger_qweb'
def _getXlsxReportName(self):
return 'a_f_r.report_general_ledger_xlsx'
def _getXlsxReportActionName(self):
return 'account_financial_report.' \
'action_report_general_ledger_xlsx'
def _getReportTitle(self):
return 'Odoo Report'
def _getBaseFilters(self):
return {
'date_from': time.strftime('%Y-01-01'),
'date_to': time.strftime('%Y-12-31'),
'company_id': self.env.ref('base.main_company').id,
'fy_start_date': time.strftime('%Y-01-01'),
}
def _getAdditionalFiltersToBeTested(self):
return [
{'only_posted_moves': True},
{'hide_account_balance_at_0': True},
{'centralize': True},
{'only_posted_moves': True, 'hide_account_balance_at_0': True},
{'only_posted_moves': True, 'centralize': True},
{'hide_account_balance_at_0': True, 'centralize': True},
{
'only_posted_moves': True,
'hide_account_balance_at_0': True,
'centralize': True
},
]
class TestGeneralLedgerReport(TransactionCase):
def setUp(self):
super(TestGeneralLedgerReport, self).setUp()
self.before_previous_fy_year = '2014-05-05'
self.previous_fy_date_start = '2015-01-01'
self.previous_fy_date_end = '2015-12-31'
self.fy_date_start = '2016-01-01'
self.fy_date_end = '2016-12-31'
self.receivable_account = self.env['account.account'].search([
('user_type_id.name', '=', 'Receivable')
], limit=1)
self.income_account = self.env['account.account'].search([
('user_type_id.name', '=', 'Income')
], limit=1)
self.unaffected_account = self.env['account.account'].search([
(
'user_type_id',
'=',
self.env.ref('account.data_unaffected_earnings').id
)], limit=1)
def _add_move(
self,
date,
receivable_debit,
receivable_credit,
income_debit,
income_credit,
unaffected_debit=0,
unaffected_credit=0
):
move_name = 'expense accrual'
journal = self.env['account.journal'].search([
('code', '=', 'MISC')])
partner = self.env.ref('base.res_partner_12')
move_vals = {
'journal_id': journal.id,
'partner_id': partner.id,
'name': move_name,
'date': date,
'line_ids': [
(0, 0, {
'name': move_name,
'debit': receivable_debit,
'credit': receivable_credit,
'account_id': self.receivable_account.id}),
(0, 0, {
'name': move_name,
'debit': income_debit,
'credit': income_credit,
'account_id': self.income_account.id}),
(0, 0, {
'name': move_name,
'debit': unaffected_debit,
'credit': unaffected_credit,
'account_id': self.unaffected_account.id}),
]}
move = self.env['account.move'].create(move_vals)
move.post()
def _get_report_lines(self, with_partners=False):
company = self.env.ref('base.main_company')
general_ledger = self.env['report_general_ledger'].create({
'date_from': self.fy_date_start,
'date_to': self.fy_date_end,
'only_posted_moves': True,
'hide_account_balance_at_0': False,
'company_id': company.id,
'fy_start_date': self.fy_date_start,
})
general_ledger.compute_data_for_report(
with_line_details=True, with_partners=with_partners
)
lines = {}
report_account_model = self.env['report_general_ledger_account']
lines['receivable'] = report_account_model.search([
('report_id', '=', general_ledger.id),
('account_id', '=', self.receivable_account.id),
])
lines['income'] = report_account_model.search([
('report_id', '=', general_ledger.id),
('account_id', '=', self.income_account.id),
])
lines['unaffected'] = report_account_model.search([
('report_id', '=', general_ledger.id),
('account_id', '=', self.unaffected_account.id),
])
if with_partners:
report_partner_model = self.env[
'report_general_ledger_partner'
]
lines['partner_receivable'] = report_partner_model.search([
('report_account_id', '=', lines['receivable'].id),
('partner_id', '=', self.env.ref('base.res_partner_12').id),
])
return lines
def test_01_account_balance(self):
# Generate the general ledger line
lines = self._get_report_lines()
self.assertEqual(len(lines['receivable']), 0)
self.assertEqual(len(lines['income']), 0)
# Add a move at the previous day of the first day of fiscal year
# to check the initial balance
self._add_move(
date=self.previous_fy_date_end,
receivable_debit=1000,
receivable_credit=0,
income_debit=0,
income_credit=1000
)
# Re Generate the general ledger line
lines = self._get_report_lines()
self.assertEqual(len(lines['receivable']), 1)
self.assertEqual(len(lines['income']), 0)
# Check the initial and final balance
self.assertEqual(lines['receivable'].initial_debit, 1000)
self.assertEqual(lines['receivable'].initial_credit, 0)
self.assertEqual(lines['receivable'].initial_balance, 1000)
self.assertEqual(lines['receivable'].final_debit, 1000)
self.assertEqual(lines['receivable'].final_credit, 0)
self.assertEqual(lines['receivable'].final_balance, 1000)
# Add reversale move of the initial move the first day of fiscal year
# to check the first day of fiscal year is not used
# to compute the initial balance
self._add_move(
date=self.fy_date_start,
receivable_debit=0,
receivable_credit=1000,
income_debit=1000,
income_credit=0
)
# Re Generate the general ledger line
lines = self._get_report_lines()
self.assertEqual(len(lines['receivable']), 1)
self.assertEqual(len(lines['income']), 1)
# Check the initial and final balance
self.assertEqual(lines['receivable'].initial_debit, 1000)
self.assertEqual(lines['receivable'].initial_credit, 0)
self.assertEqual(lines['receivable'].initial_balance, 1000)
self.assertEqual(lines['receivable'].final_debit, 1000)
self.assertEqual(lines['receivable'].final_credit, 1000)
self.assertEqual(lines['receivable'].final_balance, 0)
self.assertEqual(lines['income'].initial_debit, 0)
self.assertEqual(lines['income'].initial_credit, 0)
self.assertEqual(lines['income'].initial_balance, 0)
self.assertEqual(lines['income'].final_debit, 1000)
self.assertEqual(lines['income'].final_credit, 0)
self.assertEqual(lines['income'].final_balance, 1000)
# Add another move at the end day of fiscal year
# to check that it correctly used on report
self._add_move(
date=self.fy_date_end,
receivable_debit=0,
receivable_credit=1000,
income_debit=1000,
income_credit=0
)
# Re Generate the general ledger line
lines = self._get_report_lines()
self.assertEqual(len(lines['receivable']), 1)
self.assertEqual(len(lines['income']), 1)
# Check the initial and final balance
self.assertEqual(lines['receivable'].initial_debit, 1000)
self.assertEqual(lines['receivable'].initial_credit, 0)
self.assertEqual(lines['receivable'].initial_balance, 1000)
self.assertEqual(lines['receivable'].final_debit, 1000)
self.assertEqual(lines['receivable'].final_credit, 2000)
self.assertEqual(lines['receivable'].final_balance, -1000)
self.assertEqual(lines['income'].initial_debit, 0)
self.assertEqual(lines['income'].initial_credit, 0)
self.assertEqual(lines['income'].initial_balance, 0)
self.assertEqual(lines['income'].final_debit, 2000)
self.assertEqual(lines['income'].final_credit, 0)
self.assertEqual(lines['income'].final_balance, 2000)
def test_02_partner_balance(self):
# Generate the general ledger line
lines = self._get_report_lines(with_partners=True)
self.assertEqual(len(lines['partner_receivable']), 0)
# Add a move at the previous day of the first day of fiscal year
# to check the initial balance
self._add_move(
date=self.previous_fy_date_end,
receivable_debit=1000,
receivable_credit=0,
income_debit=0,
income_credit=1000
)
# Re Generate the general ledger line
lines = self._get_report_lines(with_partners=True)
self.assertEqual(len(lines['partner_receivable']), 1)
# Check the initial and final balance
self.assertEqual(lines['partner_receivable'].initial_debit, 1000)
self.assertEqual(lines['partner_receivable'].initial_credit, 0)
self.assertEqual(lines['partner_receivable'].initial_balance, 1000)
self.assertEqual(lines['partner_receivable'].final_debit, 1000)
self.assertEqual(lines['partner_receivable'].final_credit, 0)
self.assertEqual(lines['partner_receivable'].final_balance, 1000)
# Add reversale move of the initial move the first day of fiscal year
# to check the first day of fiscal year is not used
# to compute the initial balance
self._add_move(
date=self.fy_date_start,
receivable_debit=0,
receivable_credit=1000,
income_debit=1000,
income_credit=0
)
# Re Generate the general ledger line
lines = self._get_report_lines(with_partners=True)
self.assertEqual(len(lines['partner_receivable']), 1)
# Check the initial and final balance
self.assertEqual(lines['partner_receivable'].initial_debit, 1000)
self.assertEqual(lines['partner_receivable'].initial_credit, 0)
self.assertEqual(lines['partner_receivable'].initial_balance, 1000)
self.assertEqual(lines['partner_receivable'].final_debit, 1000)
self.assertEqual(lines['partner_receivable'].final_credit, 1000)
self.assertEqual(lines['partner_receivable'].final_balance, 0)
# Add another move at the end day of fiscal year
# to check that it correctly used on report
self._add_move(
date=self.fy_date_end,
receivable_debit=0,
receivable_credit=1000,
income_debit=1000,
income_credit=0
)
# Re Generate the general ledger line
lines = self._get_report_lines(with_partners=True)
self.assertEqual(len(lines['partner_receivable']), 1)
# Check the initial and final balance
self.assertEqual(lines['partner_receivable'].initial_debit, 1000)
self.assertEqual(lines['partner_receivable'].initial_credit, 0)
self.assertEqual(lines['partner_receivable'].initial_balance, 1000)
self.assertEqual(lines['partner_receivable'].final_debit, 1000)
self.assertEqual(lines['partner_receivable'].final_credit, 2000)
self.assertEqual(lines['partner_receivable'].final_balance, -1000)
def test_03_unaffected_account_balance(self):
# Generate the general ledger line
lines = self._get_report_lines()
self.assertEqual(len(lines['unaffected']), 1)
# Check the initial and final balance
self.assertEqual(lines['unaffected'].initial_debit, 0)
self.assertEqual(lines['unaffected'].initial_credit, 0)
self.assertEqual(lines['unaffected'].initial_balance, 0)
self.assertEqual(lines['unaffected'].final_debit, 0)
self.assertEqual(lines['unaffected'].final_credit, 0)
self.assertEqual(lines['unaffected'].final_balance, 0)
# Add a move at the previous day of the first day of fiscal year
# to check the initial balance
self._add_move(
date=self.previous_fy_date_end,
receivable_debit=1000,
receivable_credit=0,
income_debit=0,
income_credit=1000
)
# Re Generate the general ledger line
lines = self._get_report_lines()
self.assertEqual(len(lines['unaffected']), 1)
# Check the initial and final balance
self.assertEqual(lines['unaffected'].initial_debit, 0)
self.assertEqual(lines['unaffected'].initial_credit, 0)
self.assertEqual(lines['unaffected'].initial_balance, -1000)
self.assertEqual(lines['unaffected'].final_debit, -0)
self.assertEqual(lines['unaffected'].final_credit, 0)
self.assertEqual(lines['unaffected'].final_balance, -1000)
# Add reversale move of the initial move the first day of fiscal year
# to check the first day of fiscal year is not used
# to compute the initial balance
self._add_move(
date=self.fy_date_start,
receivable_debit=0,
receivable_credit=0,
income_debit=0,
income_credit=1000,
unaffected_debit=1000,
unaffected_credit=0
)
# Re Generate the general ledger line
lines = self._get_report_lines()
self.assertEqual(len(lines['unaffected']), 1)
# Check the initial and final balance
self.assertEqual(lines['unaffected'].initial_debit, 0)
self.assertEqual(lines['unaffected'].initial_credit, 0)
self.assertEqual(lines['unaffected'].initial_balance, -1000)
self.assertEqual(lines['unaffected'].final_debit, 1000)
self.assertEqual(lines['unaffected'].final_credit, 0)
self.assertEqual(lines['unaffected'].final_balance, 0)
# Add another move at the end day of fiscal year
# to check that it correctly used on report
self._add_move(
date=self.fy_date_end,
receivable_debit=3000,
receivable_credit=0,
income_debit=0,
income_credit=0,
unaffected_debit=0,
unaffected_credit=3000
)
# Re Generate the general ledger line
lines = self._get_report_lines()
self.assertEqual(len(lines['unaffected']), 1)
# Check the initial and final balance
self.assertEqual(lines['unaffected'].initial_debit, 0)
self.assertEqual(lines['unaffected'].initial_credit, 0)
self.assertEqual(lines['unaffected'].initial_balance, -1000)
self.assertEqual(lines['unaffected'].final_debit, 1000)
self.assertEqual(lines['unaffected'].final_credit, 3000)
self.assertEqual(lines['unaffected'].final_balance, -3000)
def test_04_unaffected_account_balance_2_years(self):
# Generate the general ledger line
lines = self._get_report_lines()
self.assertEqual(len(lines['unaffected']), 1)
# Check the initial and final balance
self.assertEqual(lines['unaffected'].initial_debit, 0)
self.assertEqual(lines['unaffected'].initial_credit, 0)
self.assertEqual(lines['unaffected'].initial_balance, 0)
self.assertEqual(lines['unaffected'].final_debit, 0)
self.assertEqual(lines['unaffected'].final_credit, 0)
self.assertEqual(lines['unaffected'].final_balance, 0)
# Add a move at any date 2 years before the balance
# (to create an historic)
self._add_move(
date=self.before_previous_fy_year,
receivable_debit=0,
receivable_credit=1000,
income_debit=1000,
income_credit=0
)
# Re Generate the general ledger line
lines = self._get_report_lines()
self.assertEqual(len(lines['unaffected']), 1)
# Check the initial and final balance
self.assertEqual(lines['unaffected'].initial_debit, 0)
self.assertEqual(lines['unaffected'].initial_credit, 0)
self.assertEqual(lines['unaffected'].initial_balance, 1000)
self.assertEqual(lines['unaffected'].final_debit, 0)
self.assertEqual(lines['unaffected'].final_credit, 0)
self.assertEqual(lines['unaffected'].final_balance, 1000)
# Affect the company's result last year
self._add_move(
date=self.previous_fy_date_start,
receivable_debit=1000,
receivable_credit=0,
income_debit=0,
income_credit=0,
unaffected_debit=0,
unaffected_credit=1000
)
# Add another move last year to test the initial balance this year
self._add_move(
date=self.previous_fy_date_start,
receivable_debit=0,
receivable_credit=500,
income_debit=500,
income_credit=0,
unaffected_debit=0,
unaffected_credit=0
)
# Re Generate the general ledger line
lines = self._get_report_lines()
self.assertEqual(len(lines['unaffected']), 1)
# Check the initial and final balance
self.assertEqual(lines['unaffected'].initial_debit, 0)
self.assertEqual(lines['unaffected'].initial_credit, 0)
self.assertEqual(lines['unaffected'].initial_balance, 500)
self.assertEqual(lines['unaffected'].final_debit, 0)
self.assertEqual(lines['unaffected'].final_credit, 0)
self.assertEqual(lines['unaffected'].final_balance, 500)

41
account_financial_report/tests/test_open_items.py

@ -0,0 +1,41 @@
# Author: Julien Coux
# Copyright 2016 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import time
from . import abstract_test
class TestOpenItems(abstract_test.AbstractTest):
"""
Technical tests for Open Items Report.
"""
def _getReportModel(self):
return self.env['report_open_items']
def _getQwebReportName(self):
return 'account_financial_report.report_open_items_qweb'
def _getXlsxReportName(self):
return 'a_f_r.report_open_items_xlsx'
def _getXlsxReportActionName(self):
return 'account_financial_report.action_report_open_items_xlsx'
def _getReportTitle(self):
return 'Odoo Report'
def _getBaseFilters(self):
return {
'date_at': time.strftime('%Y-12-31'),
'company_id': self.env.ref('base.main_company').id,
}
def _getAdditionalFiltersToBeTested(self):
return [
{'only_posted_moves': True},
{'hide_account_balance_at_0': True},
{'only_posted_moves': True, 'hide_account_balance_at_0': True},
]

54
account_financial_report/tests/test_trial_balance.py

@ -0,0 +1,54 @@
# Author: Julien Coux
# Copyright 2016 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import time
from . import abstract_test
class TestTrialBalance(abstract_test.AbstractTest):
"""
Technical tests for Trial Balance Report.
"""
def _getReportModel(self):
return self.env['report_trial_balance']
def _getQwebReportName(self):
return 'account_financial_report.report_trial_balance_qweb'
def _getXlsxReportName(self):
return 'a_f_r.report_trial_balance_xlsx'
def _getXlsxReportActionName(self):
return 'account_financial_report.action_report_trial_balance_xlsx'
def _getReportTitle(self):
return 'Odoo Report'
def _getBaseFilters(self):
return {
'date_from': time.strftime('%Y-01-01'),
'date_to': time.strftime('%Y-12-31'),
'company_id': self.env.ref('base.main_company').id,
'fy_start_date': time.strftime('%Y-01-01'),
}
def _getAdditionalFiltersToBeTested(self):
return [
{'only_posted_moves': True},
{'hide_account_balance_at_0': True},
{'show_partner_details': True},
{'only_posted_moves': True, 'hide_account_balance_at_0': True},
{'only_posted_moves': True, 'show_partner_details': True},
{'hide_account_balance_at_0': True, 'show_partner_details': True},
{
'only_posted_moves': True,
'hide_account_balance_at_0': True,
'show_partner_details': True
},
]
def _partner_test_is_possible(self, filters):
return 'show_partner_details' in filters

42
account_financial_report/view/account_view.xml

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record model="ir.ui.view" id="view_account_specific_form">
<field name="name">account.account.form.inherit</field>
<field name="inherit_id" ref="account.view_account_form"/>
<field name="model">account.account</field>
<field name="type">form</field>
<field name="arch" type="xml">
<field name="deprecated" position="after">
<field name="centralized"/>
</field>
</field>
</record>
<record id="action_report_general_ledger" model="ir.actions.client">
<field name="name">General Ledger Report</field>
<field name="tag">report_general_ledger</field>
<field name="context" eval="{'model': 'report_general_ledger'}" />
</record>
<record id="action_report_trial_balance" model="ir.actions.client">
<field name="name">Trial Balance Report</field>
<field name="tag">report_trial_balance</field>
<field name="context" eval="{'model': 'report_trial_balance'}" />
</record>
<record id="action_report_open_items" model="ir.actions.client">
<field name="name">Open Items Report</field>
<field name="tag">report_open_items</field>
<field name="context" eval="{'model': 'report_open_items'}" />
</record>
<record id="action_report_aged_partner_balance" model="ir.actions.client">
<field name="name">Aged Partner Balance Report</field>
<field name="tag">report_aged_partner_balance</field>
<field name="context" eval="{'model': 'report_aged_partner_balance'}" />
</record>
</odoo>

9
account_financial_report/view/report_aged_partner_balance.xml

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="report_aged_partner_balance">
<div class="container o_account_financial_reports_page">
<t t-call="account_financial_report.report_buttons"/>
<t t-call="account_financial_report.report_aged_partner_balance_base"/>
</div>
</template>
</odoo>

9
account_financial_report/view/report_general_ledger.xml

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="report_general_ledger">
<div class="container o_account_financial_reports_page">
<t t-call="account_financial_report.report_buttons"/>
<t t-call="account_financial_report.report_general_ledger_base"/>
</div>
</template>
</odoo>

9
account_financial_report/view/report_open_items.xml

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="report_open_items">
<div class="container o_account_financial_reports_page">
<t t-call="account_financial_report.report_buttons"/>
<t t-call="account_financial_report.report_open_items_base"/>
</div>
</template>
</odoo>

45
account_financial_report/view/report_template.xml

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="account_financial_report_assets_backend"
name="account_financial_report assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<link href="/account_financial_report/static/src/css/report.css" rel="stylesheet"/>
<script type="text/javascript"
src="/account_financial_report/static/src/js/account_financial_report_backend.js"/>
<script type="text/javascript"
src="/account_financial_report/static/src/js/account_financial_report_widgets.js"/>
</xpath>
</template>
<template id="report_buttons">
<div class="button_row">
<button class="o_account_financial_reports_print btn btn-sm oe_button"><span class="fa fa-print"/> Print</button>
<button class="o_account_financial_reports_export btn btn-sm oe_button"><span class="fa fa-download"/> Export</button>
</div>
</template>
<record id="action_report_general_ledger" model="ir.actions.client">
<field name="name">General Ledger</field>
<field name="tag">account_financial_report_backend</field>
<field name="context" eval="{'active_model': 'report_general_ledger'}" />
</record>
<record id="action_report_open_items" model="ir.actions.client">
<field name="name">Open Items</field>
<field name="tag">account_financial_report_backend</field>
<field name="context" eval="{'active_model': 'report_open_items'}" />
</record>
<record id="action_report_trial_balance" model="ir.actions.client">
<field name="name">Trial Balance</field>
<field name="tag">account_financial_report_backend</field>
<field name="context" eval="{'active_model': 'report_trial_balance'}" />
</record>
<record id="action_report_aged_partner_balance" model="ir.actions.client">
<field name="name">Aged Partner Balance</field>
<field name="tag">account_financial_report_backend</field>
<field name="context" eval="{'active_model': 'report_aged_partner_balance'}" />
</record>
</odoo>

9
account_financial_report/view/report_trial_balance.xml

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="report_trial_balance">
<div class="container o_account_financial_reports_page">
<t t-call="account_financial_report.report_buttons"/>
<t t-call="account_financial_report.report_trial_balance_base"/>
</div>
</template>
</odoo>

9
account_financial_report/wizard/__init__.py

@ -0,0 +1,9 @@
# Author: Damien Crier
# Author: Julien Coux
# Copyright 2016 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import aged_partner_balance_wizard
from . import general_ledger_wizard
from . import open_items_wizard
from . import trial_balance_wizard

103
account_financial_report/wizard/aged_partner_balance_wizard.py

@ -0,0 +1,103 @@
# Author: Damien Crier, Andrea Stirpe, Kevin Graveman, Dennis Sluijk
# Author: Julien Coux
# Copyright 2016 Camptocamp SA, Onestein B.V.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from datetime import datetime
from odoo import api, fields, models
from odoo.tools.safe_eval import safe_eval
from odoo.tools import pycompat
class AgedPartnerBalance(models.TransientModel):
"""Aged partner balance report wizard."""
_name = 'aged.partner.balance.wizard'
_description = 'Aged Partner Balance Wizard'
company_id = fields.Many2one(
comodel_name='res.company',
default=lambda self: self.env.user.company_id,
string='Company'
)
date_at = fields.Date(required=True,
default=fields.Date.to_string(datetime.today()))
target_move = fields.Selection([('posted', 'All Posted Entries'),
('all', 'All Entries')],
string='Target Moves',
required=True,
default='all')
account_ids = fields.Many2many(
comodel_name='account.account',
string='Filter accounts',
)
receivable_accounts_only = fields.Boolean()
payable_accounts_only = fields.Boolean()
partner_ids = fields.Many2many(
comodel_name='res.partner',
string='Filter partners',
)
show_move_line_details = fields.Boolean()
@api.onchange('receivable_accounts_only', 'payable_accounts_only')
def onchange_type_accounts_only(self):
"""Handle receivable/payable accounts only change."""
if self.receivable_accounts_only or self.payable_accounts_only:
domain = []
if self.receivable_accounts_only and self.payable_accounts_only:
domain += [('internal_type', 'in', ('receivable', 'payable'))]
elif self.receivable_accounts_only:
domain += [('internal_type', '=', 'receivable')]
elif self.payable_accounts_only:
domain += [('internal_type', '=', 'payable')]
self.account_ids = self.env['account.account'].search(domain)
else:
self.account_ids = None
@api.multi
def button_export_html(self):
self.ensure_one()
action = self.env.ref(
'account_financial_report.action_report_aged_partner_balance')
vals = action.read()[0]
context1 = vals.get('context', {})
if isinstance(context1, pycompat.string_types):
context1 = safe_eval(context1)
model = self.env['report_aged_partner_balance']
report = model.create(self._prepare_report_aged_partner_balance())
report.compute_data_for_report()
context1['active_id'] = report.id
context1['active_ids'] = report.ids
vals['context'] = context1
return vals
@api.multi
def button_export_pdf(self):
self.ensure_one()
report_type = 'qweb-pdf'
return self._export(report_type)
@api.multi
def button_export_xlsx(self):
self.ensure_one()
report_type = 'xlsx'
return self._export(report_type)
def _prepare_report_aged_partner_balance(self):
self.ensure_one()
return {
'date_at': self.date_at,
'only_posted_moves': self.target_move == 'posted',
'company_id': self.company_id.id,
'filter_account_ids': [(6, 0, self.account_ids.ids)],
'filter_partner_ids': [(6, 0, self.partner_ids.ids)],
'show_move_line_details': self.show_move_line_details,
}
def _export(self, report_type):
"""Default export is PDF."""
model = self.env['report_aged_partner_balance']
report = model.create(self._prepare_report_aged_partner_balance())
report.compute_data_for_report()
return report.print_report(report_type)

53
account_financial_report/wizard/aged_partner_balance_wizard_view.xml

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- AGED PARTNER BALANCE -->
<record id="aged_partner_balance_wizard" model="ir.ui.view">
<field name="name">Aged Partner Balance</field>
<field name="model">aged.partner.balance.wizard</field>
<field name="arch" type="xml">
<form>
<group name="main_info">
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
</group>
<group name="filters">
<group name="date_range">
<field name="date_at"/>
</group>
<group name="other_filters">
<field name="target_move" widget="radio"/>
<field name="show_move_line_details"/>
</group>
</group>
<label for="partner_ids"/>
<field name="partner_ids" nolabel="1" options="{'no_create': True}"/>
<group/>
<label for="account_ids"/>
<group col="4">
<field name="receivable_accounts_only"/>
<field name="payable_accounts_only"/>
</group>
<field name="account_ids" widget="many2many_tags" nolabel="1" options="{'no_create': True}"/>
<footer>
<button name="button_export_html" string="View"
type="object" default_focus="1" class="oe_highlight"/>
or
<button name="button_export_pdf" string="Export PDF" type="object"/>
or
<button name="button_export_xlsx" string="Export XLSX" type="object"/>
or
<button string="Cancel" class="oe_link" special="cancel" />
</footer>
</form>
</field>
</record>
<act_window id="action_aged_partner_balance_wizard"
name="Aged Partner Balance"
res_model="aged.partner.balance.wizard"
view_type="form"
view_mode="form"
view_id="aged_partner_balance_wizard"
target="new" />
</odoo>

163
account_financial_report/wizard/general_ledger_wizard.py

@ -0,0 +1,163 @@
# Author: Damien Crier
# Author: Julien Coux
# Author: Jordi Ballester
# Copyright 2016 Camptocamp SA
# Copyright 2017 Akretion - Alexis de Lattre
# Copyright 2017 Eficent Business and IT Consulting Services, S.L.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import api, fields, models
from odoo.tools.safe_eval import safe_eval
from odoo.tools import pycompat
class GeneralLedgerReportWizard(models.TransientModel):
"""General ledger report wizard."""
_name = "general.ledger.report.wizard"
_description = "General Ledger Report Wizard"
company_id = fields.Many2one(
comodel_name='res.company',
default=lambda self: self.env.user.company_id,
string='Company'
)
date_range_id = fields.Many2one(
comodel_name='date.range',
string='Date range'
)
date_from = fields.Date(required=True)
date_to = fields.Date(required=True)
fy_start_date = fields.Date(compute='_compute_fy_start_date')
target_move = fields.Selection([('posted', 'All Posted Entries'),
('all', 'All Entries')],
string='Target Moves',
required=True,
default='all')
account_ids = fields.Many2many(
comodel_name='account.account',
string='Filter accounts',
)
centralize = fields.Boolean(string='Activate centralization',
default=True)
hide_account_balance_at_0 = fields.Boolean(
string='Hide account ending balance at 0',
help='Use this filter to hide an account or a partner '
'with an ending balance at 0. '
'If partners are filtered, '
'debits and credits totals will not match the trial balance.'
)
receivable_accounts_only = fields.Boolean()
payable_accounts_only = fields.Boolean()
partner_ids = fields.Many2many(
comodel_name='res.partner',
string='Filter partners',
)
cost_center_ids = fields.Many2many(
comodel_name='account.analytic.account',
string='Filter cost centers',
)
not_only_one_unaffected_earnings_account = fields.Boolean(
readonly=True,
string='Not only one unaffected earnings account'
)
@api.depends('date_from')
def _compute_fy_start_date(self):
for wiz in self.filtered('date_from'):
date = fields.Datetime.from_string(wiz.date_from)
res = self.company_id.compute_fiscalyear_dates(date)
wiz.fy_start_date = res['date_from']
@api.onchange('company_id')
def onchange_company_id(self):
"""Handle company change."""
account_type = self.env.ref('account.data_unaffected_earnings')
count = self.env['account.account'].search_count(
[
('user_type_id', '=', account_type.id),
('company_id', '=', self.company_id.id)
])
self.not_only_one_unaffected_earnings_account = count != 1
@api.onchange('date_range_id')
def onchange_date_range_id(self):
"""Handle date range change."""
self.date_from = self.date_range_id.date_start
self.date_to = self.date_range_id.date_end
@api.onchange('receivable_accounts_only', 'payable_accounts_only')
def onchange_type_accounts_only(self):
"""Handle receivable/payable accounts only change."""
if self.receivable_accounts_only or self.payable_accounts_only:
domain = []
if self.receivable_accounts_only and self.payable_accounts_only:
domain += [('internal_type', 'in', ('receivable', 'payable'))]
elif self.receivable_accounts_only:
domain += [('internal_type', '=', 'receivable')]
elif self.payable_accounts_only:
domain += [('internal_type', '=', 'payable')]
self.account_ids = self.env['account.account'].search(domain)
else:
self.account_ids = None
@api.onchange('partner_ids')
def onchange_partner_ids(self):
"""Handle partners change."""
if self.partner_ids:
self.receivable_accounts_only = self.payable_accounts_only = True
else:
self.receivable_accounts_only = self.payable_accounts_only = False
@api.multi
def button_export_html(self):
self.ensure_one()
action = self.env.ref(
'account_financial_report.action_report_general_ledger')
vals = action.read()[0]
context1 = vals.get('context', {})
if isinstance(context1, pycompat.string_types):
context1 = safe_eval(context1)
model = self.env['report_general_ledger']
report = model.create(self._prepare_report_general_ledger())
report.compute_data_for_report()
context1['active_id'] = report.id
context1['active_ids'] = report.ids
vals['context'] = context1
return vals
@api.multi
def button_export_pdf(self):
self.ensure_one()
report_type = 'qweb-pdf'
return self._export(report_type)
@api.multi
def button_export_xlsx(self):
self.ensure_one()
report_type = 'xlsx'
return self._export(report_type)
def _prepare_report_general_ledger(self):
self.ensure_one()
return {
'date_from': self.date_from,
'date_to': self.date_to,
'only_posted_moves': self.target_move == 'posted',
'hide_account_balance_at_0': self.hide_account_balance_at_0,
'company_id': self.company_id.id,
'filter_account_ids': [(6, 0, self.account_ids.ids)],
'filter_partner_ids': [(6, 0, self.partner_ids.ids)],
'filter_cost_center_ids': [(6, 0, self.cost_center_ids.ids)],
'centralize': self.centralize,
'fy_start_date': self.fy_start_date,
}
def _export(self, report_type):
"""Default export is PDF."""
model = self.env['report_general_ledger']
report = model.create(self._prepare_report_general_ledger())
report.compute_data_for_report()
return report.print_report(report_type)

73
account_financial_report/wizard/general_ledger_wizard_view.xml

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- GENERAL LEDGER -->
<record id="general_ledger_wizard" model="ir.ui.view">
<field name="name">General Ledger</field>
<field name="model">general.ledger.report.wizard</field>
<field name="arch" type="xml">
<form>
<group name="main_info">
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
</group>
<div attrs="{'invisible': [('not_only_one_unaffected_earnings_account', '=', True)]}">
<group name="filters">
<group name="date_range">
<field name="date_range_id" domain="['|',('company_id','=',company_id), ('company_id','=',False)]"/>
<field name="date_from"/>
<field name="date_to"/>
<field name="fy_start_date" invisible="1"/>
</group>
<group name="other_filters">
<field name="target_move" widget="radio"/>
<field name="centralize"/>
<field name="hide_account_balance_at_0"/>
</group>
</group>
<label for="cost_center_ids" groups="analytic.group_analytic_accounting"/>
<field name="cost_center_ids" nolabel="1" options="{'no_create': True}" groups="analytic.group_analytic_accounting"/>
<group/>
<label for="partner_ids"/>
<field name="partner_ids" nolabel="1" options="{'no_create': True}"/>
<group/>
<label for="account_ids"/>
<group col="4">
<field name="receivable_accounts_only"/>
<field name="payable_accounts_only"/>
</group>
<field name="account_ids" widget="many2many_tags" nolabel="1" options="{'no_create': True}"/>
</div>
<div attrs="{'invisible': [('not_only_one_unaffected_earnings_account', '=', False)]}">
<field name="not_only_one_unaffected_earnings_account" invisible="1"/>
<group/>
<h4>General Ledger can be computed only if selected company have only one unaffected earnings account.</h4>
<group/>
</div>
<footer>
<div attrs="{'invisible': [('not_only_one_unaffected_earnings_account', '=', True)]}">
<button name="button_export_html" string="View"
type="object" default_focus="1" class="oe_highlight"/>
or
<button name="button_export_pdf" string="Export PDF" type="object"/>
or
<button name="button_export_xlsx" string="Export XLSX" type="object"/>
or
<button string="Cancel" class="oe_link" special="cancel" />
</div>
<div attrs="{'invisible': [('not_only_one_unaffected_earnings_account', '=', False)]}">
<button string="Cancel" class="oe_link" special="cancel" />
</div>
</footer>
</form>
</field>
</record>
<act_window id="action_general_ledger_wizard"
name="General Ledger"
res_model="general.ledger.report.wizard"
view_type="form"
view_mode="form"
view_id="general_ledger_wizard"
target="new" />
</odoo>

111
account_financial_report/wizard/open_items_wizard.py

@ -0,0 +1,111 @@
# Author: Damien Crier
# Author: Julien Coux
# Copyright 2016 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from datetime import datetime
from odoo import models, fields, api
from odoo.tools.safe_eval import safe_eval
from odoo.tools import pycompat
class OpenItemsReportWizard(models.TransientModel):
"""Open items report wizard."""
_name = "open.items.report.wizard"
_description = "Open Items Report Wizard"
company_id = fields.Many2one(
comodel_name='res.company',
default=lambda self: self.env.user.company_id,
string='Company'
)
date_at = fields.Date(required=True,
default=fields.Date.to_string(datetime.today()))
target_move = fields.Selection([('posted', 'All Posted Entries'),
('all', 'All Entries')],
string='Target Moves',
required=True,
default='all')
account_ids = fields.Many2many(
comodel_name='account.account',
string='Filter accounts',
domain=[('reconcile', '=', True)],
)
hide_account_balance_at_0 = fields.Boolean(
string='Hide account ending balance at 0',
help='Use this filter to hide an account or a partner '
'with an ending balance at 0. '
'If partners are filtered, '
'debits and credits totals will not match the trial balance.'
)
receivable_accounts_only = fields.Boolean()
payable_accounts_only = fields.Boolean()
partner_ids = fields.Many2many(
comodel_name='res.partner',
string='Filter partners',
)
@api.onchange('receivable_accounts_only', 'payable_accounts_only')
def onchange_type_accounts_only(self):
"""Handle receivable/payable accounts only change."""
if self.receivable_accounts_only or self.payable_accounts_only:
domain = []
if self.receivable_accounts_only and self.payable_accounts_only:
domain += [('internal_type', 'in', ('receivable', 'payable'))]
elif self.receivable_accounts_only:
domain += [('internal_type', '=', 'receivable')]
elif self.payable_accounts_only:
domain += [('internal_type', '=', 'payable')]
self.account_ids = self.env['account.account'].search(domain)
else:
self.account_ids = None
@api.multi
def button_export_html(self):
self.ensure_one()
action = self.env.ref(
'account_financial_report.action_report_open_items')
vals = action.read()[0]
context1 = vals.get('context', {})
if isinstance(context1, pycompat.string_types):
context1 = safe_eval(context1)
model = self.env['report_open_items']
report = model.create(self._prepare_report_open_items())
report.compute_data_for_report()
context1['active_id'] = report.id
context1['active_ids'] = report.ids
vals['context'] = context1
return vals
@api.multi
def button_export_pdf(self):
self.ensure_one()
report_type = 'qweb-pdf'
return self._export(report_type)
@api.multi
def button_export_xlsx(self):
self.ensure_one()
report_type = 'xlsx'
return self._export(report_type)
def _prepare_report_open_items(self):
self.ensure_one()
return {
'date_at': self.date_at,
'only_posted_moves': self.target_move == 'posted',
'hide_account_balance_at_0': self.hide_account_balance_at_0,
'company_id': self.company_id.id,
'filter_account_ids': [(6, 0, self.account_ids.ids)],
'filter_partner_ids': [(6, 0, self.partner_ids.ids)],
}
def _export(self, report_type):
"""Default export is PDF."""
model = self.env['report_open_items']
report = model.create(self._prepare_report_open_items())
report.compute_data_for_report()
return report.print_report(report_type)

53
account_financial_report/wizard/open_items_wizard_view.xml

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- OPEN ITEMS -->
<record id="open_items_wizard" model="ir.ui.view">
<field name="name">Open Items</field>
<field name="model">open.items.report.wizard</field>
<field name="arch" type="xml">
<form>
<group name="main_info">
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
</group>
<group name="filters">
<group name="date_range">
<field name="date_at"/>
</group>
<group name="other_filters">
<field name="target_move" widget="radio"/>
<field name="hide_account_balance_at_0"/>
</group>
</group>
<label for="partner_ids"/>
<field name="partner_ids" nolabel="1" options="{'no_create': True}"/>
<group/>
<label for="account_ids"/>
<group col="4">
<field name="receivable_accounts_only"/>
<field name="payable_accounts_only"/>
</group>
<field name="account_ids" widget="many2many_tags" nolabel="1" options="{'no_create': True}"/>
<footer>
<button name="button_export_html" string="View"
type="object" default_focus="1" class="oe_highlight"/>
or
<button name="button_export_pdf" string="Export PDF" type="object"/>
or
<button name="button_export_xlsx" string="Export XLSX" type="object"/>
or
<button string="Cancel" class="oe_link" special="cancel" />
</footer>
</form>
</field>
</record>
<act_window id="action_open_items_wizard"
name="Open Items"
res_model="open.items.report.wizard"
view_type="form"
view_mode="form"
view_id="open_items_wizard"
target="new" />
</odoo>

155
account_financial_report/wizard/trial_balance_wizard.py

@ -0,0 +1,155 @@
# Author: Julien Coux
# Copyright 2016 Camptocamp SA
# Copyright 2017 Akretion - Alexis de Lattre
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import models, fields, api
from odoo.tools.safe_eval import safe_eval
from odoo.tools import pycompat
class TrialBalanceReportWizard(models.TransientModel):
"""Trial balance report wizard."""
_name = "trial.balance.report.wizard"
_description = "Trial Balance Report Wizard"
company_id = fields.Many2one(
comodel_name='res.company',
default=lambda self: self.env.user.company_id,
string='Company'
)
date_range_id = fields.Many2one(
comodel_name='date.range',
string='Date range'
)
date_from = fields.Date(required=True)
date_to = fields.Date(required=True)
fy_start_date = fields.Date(compute='_compute_fy_start_date')
target_move = fields.Selection([('posted', 'All Posted Entries'),
('all', 'All Entries')],
string='Target Moves',
required=True,
default='all')
account_ids = fields.Many2many(
comodel_name='account.account',
string='Filter accounts',
)
hide_account_balance_at_0 = fields.Boolean(
string='Hide account ending balance at 0',
help='Use this filter to hide an account or a partner '
'with an ending balance at 0. '
'If partners are filtered, '
'debits and credits totals will not match the trial balance.'
)
receivable_accounts_only = fields.Boolean()
payable_accounts_only = fields.Boolean()
show_partner_details = fields.Boolean()
partner_ids = fields.Many2many(
comodel_name='res.partner',
string='Filter partners',
)
not_only_one_unaffected_earnings_account = fields.Boolean(
readonly=True,
string='Not only one unaffected earnings account'
)
@api.depends('date_from')
def _compute_fy_start_date(self):
for wiz in self.filtered('date_from'):
date = fields.Datetime.from_string(wiz.date_from)
res = self.company_id.compute_fiscalyear_dates(date)
wiz.fy_start_date = res['date_from']
@api.onchange('company_id')
def onchange_company_id(self):
"""Handle company change."""
account_type = self.env.ref('account.data_unaffected_earnings')
count = self.env['account.account'].search_count(
[
('user_type_id', '=', account_type.id),
('company_id', '=', self.company_id.id)
])
self.not_only_one_unaffected_earnings_account = count != 1
@api.onchange('date_range_id')
def onchange_date_range_id(self):
"""Handle date range change."""
self.date_from = self.date_range_id.date_start
self.date_to = self.date_range_id.date_end
@api.onchange('receivable_accounts_only', 'payable_accounts_only')
def onchange_type_accounts_only(self):
"""Handle receivable/payable accounts only change."""
if self.receivable_accounts_only or self.payable_accounts_only:
domain = []
if self.receivable_accounts_only and self.payable_accounts_only:
domain += [('internal_type', 'in', ('receivable', 'payable'))]
elif self.receivable_accounts_only:
domain += [('internal_type', '=', 'receivable')]
elif self.payable_accounts_only:
domain += [('internal_type', '=', 'payable')]
self.account_ids = self.env['account.account'].search(domain)
else:
self.account_ids = None
@api.onchange('show_partner_details')
def onchange_show_partner_details(self):
"""Handle partners change."""
if self.show_partner_details:
self.receivable_accounts_only = self.payable_accounts_only = True
else:
self.receivable_accounts_only = self.payable_accounts_only = False
@api.multi
def button_export_html(self):
self.ensure_one()
action = self.env.ref(
'account_financial_report.action_report_trial_balance')
vals = action.read()[0]
context1 = vals.get('context', {})
if isinstance(context1, pycompat.string_types):
context1 = safe_eval(context1)
model = self.env['report_trial_balance']
report = model.create(self._prepare_report_trial_balance())
report.compute_data_for_report()
context1['active_id'] = report.id
context1['active_ids'] = report.ids
vals['context'] = context1
return vals
@api.multi
def button_export_pdf(self):
self.ensure_one()
report_type = 'qweb-pdf'
return self._export(report_type)
@api.multi
def button_export_xlsx(self):
self.ensure_one()
report_type = 'xlsx'
return self._export(report_type)
def _prepare_report_trial_balance(self):
self.ensure_one()
return {
'date_from': self.date_from,
'date_to': self.date_to,
'only_posted_moves': self.target_move == 'posted',
'hide_account_balance_at_0': self.hide_account_balance_at_0,
'company_id': self.company_id.id,
'filter_account_ids': [(6, 0, self.account_ids.ids)],
'filter_partner_ids': [(6, 0, self.partner_ids.ids)],
'fy_start_date': self.fy_start_date,
'show_partner_details': self.show_partner_details,
}
def _export(self, report_type):
"""Default export is PDF."""
model = self.env['report_trial_balance']
report = model.create(self._prepare_report_trial_balance())
report.compute_data_for_report()
return report.print_report(report_type)

70
account_financial_report/wizard/trial_balance_wizard_view.xml

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- TRIAL BALANCE -->
<record id="trial_balance_wizard" model="ir.ui.view">
<field name="name">Trial Balance</field>
<field name="model">trial.balance.report.wizard</field>
<field name="arch" type="xml">
<form>
<group name="main_info">
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
</group>
<div attrs="{'invisible': [('not_only_one_unaffected_earnings_account', '=', True)]}">
<group name="filters">
<group name="date_range">
<field name="date_range_id" domain="['|',('company_id','=',company_id), ('company_id','=',False)]"/>
<field name="date_from"/>
<field name="date_to"/>
<field name="fy_start_date" invisible="1"/>
</group>
<group name="other_filters">
<field name="target_move" widget="radio"/>
<field name="hide_account_balance_at_0"/>
<field name="show_partner_details"/>
</group>
</group>
<label for="partner_ids" attrs="{'invisible':[('show_partner_details','!=',True)]}"/>
<field name="partner_ids" nolabel="1" options="{'no_create': True}" attrs="{'invisible':[('show_partner_details','!=',True)]}"/>
<group attrs="{'invisible':[('show_partner_details','!=',True)]}"/>
<label for="account_ids"/>
<group col="4">
<field name="receivable_accounts_only"/>
<field name="payable_accounts_only"/>
</group>
<field name="account_ids" widget="many2many_tags" nolabel="1" options="{'no_create': True}"/>
</div>
<div attrs="{'invisible': [('not_only_one_unaffected_earnings_account', '=', False)]}">
<field name="not_only_one_unaffected_earnings_account" invisible="1"/>
<group/>
<h4>Trial Balance can be computed only if selected company have only one unaffected earnings account.</h4>
<group/>
</div>
<footer>
<div attrs="{'invisible': [('not_only_one_unaffected_earnings_account', '=', True)]}">
<button name="button_export_html" string="View"
type="object" default_focus="1" class="oe_highlight"/>
or
<button name="button_export_pdf" string="Export PDF" type="object"/>
or
<button name="button_export_xlsx" string="Export XLSX" type="object"/>
or
<button string="Cancel" class="oe_link" special="cancel" />
</div>
<div attrs="{'invisible': [('not_only_one_unaffected_earnings_account', '=', False)]}">
<button string="Cancel" class="oe_link" special="cancel" />
</div>
</footer>
</form>
</field>
</record>
<act_window id="action_trial_balance_wizard"
name="Trial Balance"
res_model="trial.balance.report.wizard"
view_type="form"
view_mode="form"
view_id="trial_balance_wizard"
target="new" />
</odoo>

2
account_tax_balance/models/account_tax.py

@ -39,7 +39,7 @@ class AccountTax(models.Model):
context.get('from_date', fields.Date.context_today(self)),
context.get('to_date', fields.Date.context_today(self)),
context.get('company_id', self.env.user.company_id.id),
context.get('target_move', 'posted')
context.get('target_move', 'posted'),
)
def _account_tax_ids_with_moves(self):

3
oca_dependencies.txt

@ -1 +1,4 @@
server-ux
account-financial-tools https://github.com/Eficent/account-financial-tools 11.0-mig-account_fiscal_year
reporting-engine https://github.com/OCA/reporting-engine
server-ux https://github.com/OCA/server-ux

2
requirements.txt

@ -0,0 +1,2 @@
xlsxwriter
xlrd
Loading…
Cancel
Save