Browse Source

[IMP] partner_statement: black, isort

pull/654/head
mreficent 4 years ago
parent
commit
320791be21
  1. 38
      partner_statement/__manifest__.py
  2. 2
      partner_statement/readme/DESCRIPTION.rst
  3. 95
      partner_statement/report/activity_statement.py
  4. 55
      partner_statement/report/outstanding_statement.py
  5. 300
      partner_statement/report/report_statement_common.py
  6. 9
      partner_statement/security/statement_security.xml
  7. 6
      partner_statement/static/src/scss/layout_statement.scss
  8. 96
      partner_statement/tests/test_activity_statement.py
  9. 78
      partner_statement/tests/test_outstanding_statement.py
  10. 31
      partner_statement/tests/test_res_config_settings.py
  11. 228
      partner_statement/views/activity_statement.xml
  12. 43
      partner_statement/views/aging_buckets.xml
  13. 16
      partner_statement/views/assets.xml
  14. 221
      partner_statement/views/outstanding_statement.xml
  15. 64
      partner_statement/views/res_config_settings.xml
  16. 21
      partner_statement/wizard/activity_statement_wizard.py
  17. 11
      partner_statement/wizard/outstanding_statement_wizard.py
  18. 32
      partner_statement/wizard/res_config_settings.py
  19. 54
      partner_statement/wizard/statement_common.py
  20. 104
      partner_statement/wizard/statement_wizard.xml

38
partner_statement/__manifest__.py

@ -3,25 +3,23 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
{ {
'name': 'Partner Statement',
'version': '12.0.1.0.5',
'category': 'Accounting & Finance',
'summary': 'OCA Financial Reports',
'author': "Eficent, Odoo Community Association (OCA)",
'website': 'https://github.com/OCA/account-financial-reporting',
'license': 'AGPL-3',
'depends': [
'account',
"name": "Partner Statement",
"version": "12.0.1.0.5",
"category": "Accounting & Finance",
"summary": "OCA Financial Reports",
"author": "Eficent, Odoo Community Association (OCA)",
"website": "https://github.com/OCA/account-financial-reporting",
"license": "AGPL-3",
"depends": ["account"],
"data": [
"security/statement_security.xml",
"views/activity_statement.xml",
"views/outstanding_statement.xml",
"views/assets.xml",
"views/aging_buckets.xml",
"views/res_config_settings.xml",
"wizard/statement_wizard.xml",
], ],
'data': [
'security/statement_security.xml',
'views/activity_statement.xml',
'views/outstanding_statement.xml',
'views/assets.xml',
'views/aging_buckets.xml',
'views/res_config_settings.xml',
'wizard/statement_wizard.xml',
],
'installable': True,
'application': False,
"installable": True,
"application": False,
} }

2
partner_statement/readme/DESCRIPTION.rst

@ -1,4 +1,4 @@
This module extends the functionality of Invoicing to support the printing of customer and vendor statements.
This module extends the functionality of Invoicing to support the printing of customer and vendor statements.
There are two types of statements, Activity and Outstanding. Aging details can be shown in the reports, expressed in aging buckets, There are two types of statements, Activity and Outstanding. Aging details can be shown in the reports, expressed in aging buckets,
so the customer or vendor can review how much is open, due or overdue. so the customer or vendor can review how much is open, due or overdue.

95
partner_statement/report/activity_statement.py

@ -2,18 +2,21 @@
# (http://www.eficent.com) # (http://www.eficent.com)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import api, models
from collections import defaultdict from collections import defaultdict
from odoo import api, models
class ActivityStatement(models.AbstractModel): class ActivityStatement(models.AbstractModel):
"""Model of Activity Statement""" """Model of Activity Statement"""
_inherit = 'statement.common'
_name = 'report.partner_statement.activity_statement'
_inherit = "statement.common"
_name = "report.partner_statement.activity_statement"
def _initial_balance_sql_q1(self, partners, date_start, account_type): def _initial_balance_sql_q1(self, partners, date_start, account_type):
return str(self._cr.mogrify("""
return str(
self._cr.mogrify(
"""
SELECT l.partner_id, l.currency_id, l.company_id, SELECT l.partner_id, l.currency_id, l.company_id,
CASE WHEN l.currency_id is not null AND l.amount_currency > 0.0 CASE WHEN l.currency_id is not null AND l.amount_currency > 0.0
THEN sum(l.amount_currency) THEN sum(l.amount_currency)
@ -31,34 +34,50 @@ class ActivityStatement(models.AbstractModel):
AND m.state IN ('posted') AND m.state IN ('posted')
GROUP BY l.partner_id, l.currency_id, l.amount_currency, GROUP BY l.partner_id, l.currency_id, l.amount_currency,
l.company_id l.company_id
""", locals()), "utf-8")
""",
locals(),
),
"utf-8",
)
def _initial_balance_sql_q2(self, company_id): def _initial_balance_sql_q2(self, company_id):
return str(self._cr.mogrify("""
return str(
self._cr.mogrify(
"""
SELECT Q1.partner_id, debit-credit AS balance, SELECT Q1.partner_id, debit-credit AS balance,
COALESCE(Q1.currency_id, c.currency_id) AS currency_id COALESCE(Q1.currency_id, c.currency_id) AS currency_id
FROM Q1 FROM Q1
JOIN res_company c ON (c.id = Q1.company_id) JOIN res_company c ON (c.id = Q1.company_id)
WHERE c.id = %(company_id)s WHERE c.id = %(company_id)s
""", locals()), "utf-8")
""",
locals(),
),
"utf-8",
)
def _get_account_initial_balance(self, company_id, partner_ids,
date_start, account_type):
def _get_account_initial_balance(
self, company_id, partner_ids, date_start, account_type
):
balance_start = defaultdict(list) balance_start = defaultdict(list)
partners = tuple(partner_ids) partners = tuple(partner_ids)
# pylint: disable=E8103 # pylint: disable=E8103
self.env.cr.execute("""WITH Q1 AS (%s), Q2 AS (%s)
self.env.cr.execute(
"""WITH Q1 AS (%s), Q2 AS (%s)
SELECT partner_id, currency_id, balance SELECT partner_id, currency_id, balance
FROM Q2""" % (self._initial_balance_sql_q1(partners, date_start,
account_type),
self._initial_balance_sql_q2(company_id)))
FROM Q2"""
% (
self._initial_balance_sql_q1(partners, date_start, account_type),
self._initial_balance_sql_q2(company_id),
)
)
for row in self.env.cr.dictfetchall(): for row in self.env.cr.dictfetchall():
balance_start[row.pop('partner_id')].append(row)
balance_start[row.pop("partner_id")].append(row)
return balance_start return balance_start
def _display_lines_sql_q1(self, partners, date_start, date_end,
account_type):
return str(self._cr.mogrify("""
def _display_lines_sql_q1(self, partners, date_start, date_end, account_type):
return str(
self._cr.mogrify(
"""
SELECT m.name AS move_id, l.partner_id, l.date, SELECT m.name AS move_id, l.partner_id, l.date,
CASE WHEN (aj.type IN ('sale', 'purchase')) CASE WHEN (aj.type IN ('sale', 'purchase'))
THEN l.name THEN l.name
@ -104,10 +123,16 @@ class ActivityStatement(models.AbstractModel):
ELSE '' ELSE ''
END, END,
l.blocked, l.currency_id, l.amount_currency, l.company_id l.blocked, l.currency_id, l.amount_currency, l.company_id
""", locals()), "utf-8")
""",
locals(),
),
"utf-8",
)
def _display_lines_sql_q2(self, company_id): def _display_lines_sql_q2(self, company_id):
return str(self._cr.mogrify("""
return str(
self._cr.mogrify(
"""
SELECT Q1.partner_id, Q1.move_id, Q1.date, Q1.date_maturity, SELECT Q1.partner_id, Q1.move_id, Q1.date, Q1.date_maturity,
Q1.name, Q1.ref, Q1.debit, Q1.credit, Q1.name, Q1.ref, Q1.debit, Q1.credit,
Q1.debit-Q1.credit as amount, Q1.blocked, Q1.debit-Q1.credit as amount, Q1.blocked,
@ -115,36 +140,46 @@ class ActivityStatement(models.AbstractModel):
FROM Q1 FROM Q1
JOIN res_company c ON (c.id = Q1.company_id) JOIN res_company c ON (c.id = Q1.company_id)
WHERE c.id = %(company_id)s WHERE c.id = %(company_id)s
""", locals()), "utf-8")
""",
locals(),
),
"utf-8",
)
def _get_account_display_lines(self, company_id, partner_ids, date_start,
date_end, account_type):
def _get_account_display_lines(
self, company_id, partner_ids, date_start, date_end, account_type
):
res = dict(map(lambda x: (x, []), partner_ids)) res = dict(map(lambda x: (x, []), partner_ids))
partners = tuple(partner_ids) partners = tuple(partner_ids)
# pylint: disable=E8103 # pylint: disable=E8103
self.env.cr.execute("""
self.env.cr.execute(
"""
WITH Q1 AS (%s), WITH Q1 AS (%s),
Q2 AS (%s) Q2 AS (%s)
SELECT partner_id, move_id, date, date_maturity, name, ref, debit, SELECT partner_id, move_id, date, date_maturity, name, ref, debit,
credit, amount, blocked, currency_id credit, amount, blocked, currency_id
FROM Q2 FROM Q2
ORDER BY date, date_maturity, move_id""" % (
self._display_lines_sql_q1(partners, date_start, date_end,
account_type),
self._display_lines_sql_q2(company_id)))
ORDER BY date, date_maturity, move_id"""
% (
self._display_lines_sql_q1(
partners, date_start, date_end, account_type
),
self._display_lines_sql_q2(company_id),
)
)
for row in self.env.cr.dictfetchall(): for row in self.env.cr.dictfetchall():
res[row.pop('partner_id')].append(row)
res[row.pop("partner_id")].append(row)
return res return res
@api.multi @api.multi
def _get_report_values(self, docids, data): def _get_report_values(self, docids, data):
if not data: if not data:
data = {} data = {}
if 'company_id' not in data:
if "company_id" not in data:
wiz = self.env["activity.statement.wizard"].with_context( wiz = self.env["activity.statement.wizard"].with_context(
active_ids=docids, model="res.partner" active_ids=docids, model="res.partner"
) )
data.update(wiz.create({})._prepare_statement()) data.update(wiz.create({})._prepare_statement())
data['amount_field'] = 'amount'
data["amount_field"] = "amount"
return super()._get_report_values(docids, data) return super()._get_report_values(docids, data)

55
partner_statement/report/outstanding_statement.py

@ -8,12 +8,14 @@ from odoo import api, models
class OutstandingStatement(models.AbstractModel): class OutstandingStatement(models.AbstractModel):
"""Model of Outstanding Statement""" """Model of Outstanding Statement"""
_inherit = 'statement.common'
_name = 'report.partner_statement.outstanding_statement'
_inherit = "statement.common"
_name = "report.partner_statement.outstanding_statement"
def _display_lines_sql_q1(self, partners, date_end, account_type): def _display_lines_sql_q1(self, partners, date_end, account_type):
partners = tuple(partners) partners = tuple(partners)
return str(self._cr.mogrify("""
return str(
self._cr.mogrify(
"""
SELECT m.name AS move_id, l.partner_id, l.date, l.name, SELECT m.name AS move_id, l.partner_id, l.date, l.name,
l.ref, l.blocked, l.currency_id, l.company_id, l.ref, l.blocked, l.currency_id, l.company_id,
CASE WHEN (l.currency_id is not null AND l.amount_currency > 0.0) CASE WHEN (l.currency_id is not null AND l.amount_currency > 0.0)
@ -62,12 +64,16 @@ class OutstandingStatement(models.AbstractModel):
GROUP BY l.partner_id, m.name, l.date, l.date_maturity, l.name, GROUP BY l.partner_id, m.name, l.date, l.date_maturity, l.name,
l.ref, l.blocked, l.currency_id, l.ref, l.blocked, l.currency_id,
l.balance, l.amount_currency, l.company_id l.balance, l.amount_currency, l.company_id
""", locals()), "utf-8"
""",
locals(),
),
"utf-8",
) )
def _display_lines_sql_q2(self): def _display_lines_sql_q2(self):
return str( return str(
self._cr.mogrify("""
self._cr.mogrify(
"""
SELECT Q1.partner_id, Q1.currency_id, Q1.move_id, SELECT Q1.partner_id, Q1.currency_id, Q1.move_id,
Q1.date, Q1.date_maturity, Q1.debit, Q1.credit, Q1.date, Q1.date_maturity, Q1.debit, Q1.credit,
Q1.name, Q1.ref, Q1.blocked, Q1.company_id, Q1.name, Q1.ref, Q1.blocked, Q1.company_id,
@ -76,13 +82,16 @@ class OutstandingStatement(models.AbstractModel):
ELSE Q1.open_amount ELSE Q1.open_amount
END as open_amount END as open_amount
FROM Q1 FROM Q1
""", locals()
""",
locals(),
), ),
"utf-8"
"utf-8",
) )
def _display_lines_sql_q3(self, company_id): def _display_lines_sql_q3(self, company_id):
return str(self._cr.mogrify("""
return str(
self._cr.mogrify(
"""
SELECT Q2.partner_id, Q2.move_id, Q2.date, Q2.date_maturity, SELECT Q2.partner_id, Q2.move_id, Q2.date, Q2.date_maturity,
Q2.name, Q2.ref, Q2.debit, Q2.credit, Q2.name, Q2.ref, Q2.debit, Q2.credit,
Q2.debit-Q2.credit AS amount, blocked, Q2.debit-Q2.credit AS amount, blocked,
@ -91,37 +100,45 @@ class OutstandingStatement(models.AbstractModel):
FROM Q2 FROM Q2
JOIN res_company c ON (c.id = Q2.company_id) JOIN res_company c ON (c.id = Q2.company_id)
WHERE c.id = %(company_id)s AND Q2.open_amount != 0.0 WHERE c.id = %(company_id)s AND Q2.open_amount != 0.0
""", locals()), "utf-8"
""",
locals(),
),
"utf-8",
) )
def _get_account_display_lines(self, company_id, partner_ids, date_start,
date_end, account_type):
def _get_account_display_lines(
self, company_id, partner_ids, date_start, date_end, account_type
):
res = dict(map(lambda x: (x, []), partner_ids)) res = dict(map(lambda x: (x, []), partner_ids))
partners = tuple(partner_ids) partners = tuple(partner_ids)
# pylint: disable=E8103 # pylint: disable=E8103
self.env.cr.execute("""
self.env.cr.execute(
"""
WITH Q1 as (%s), WITH Q1 as (%s),
Q2 AS (%s), Q2 AS (%s),
Q3 AS (%s) Q3 AS (%s)
SELECT partner_id, currency_id, move_id, date, date_maturity, debit, SELECT partner_id, currency_id, move_id, date, date_maturity, debit,
credit, amount, open_amount, name, ref, blocked credit, amount, open_amount, name, ref, blocked
FROM Q3 FROM Q3
ORDER BY date, date_maturity, move_id""" % (
self._display_lines_sql_q1(partners, date_end, account_type),
self._display_lines_sql_q2(),
self._display_lines_sql_q3(company_id)))
ORDER BY date, date_maturity, move_id"""
% (
self._display_lines_sql_q1(partners, date_end, account_type),
self._display_lines_sql_q2(),
self._display_lines_sql_q3(company_id),
)
)
for row in self.env.cr.dictfetchall(): for row in self.env.cr.dictfetchall():
res[row.pop('partner_id')].append(row)
res[row.pop("partner_id")].append(row)
return res return res
@api.multi @api.multi
def _get_report_values(self, docids, data): def _get_report_values(self, docids, data):
if not data: if not data:
data = {} data = {}
if 'company_id' not in data:
if "company_id" not in data:
wiz = self.env["outstanding.statement.wizard"].with_context( wiz = self.env["outstanding.statement.wizard"].with_context(
active_ids=docids, model="res.partner" active_ids=docids, model="res.partner"
) )
data.update(wiz.create({})._prepare_statement()) data.update(wiz.create({})._prepare_statement())
data['amount_field'] = 'open_amount'
data["amount_field"] = "open_amount"
return super()._get_report_values(docids, data) return super()._get_report_values(docids, data)

300
partner_statement/report/report_statement_common.py

@ -3,39 +3,42 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from datetime import datetime, timedelta from datetime import datetime, timedelta
from odoo import _, api, fields, models
from odoo.tools.misc import DEFAULT_SERVER_DATE_FORMAT from odoo.tools.misc import DEFAULT_SERVER_DATE_FORMAT
from odoo import api, fields, models, _
class ReportStatementCommon(models.AbstractModel): class ReportStatementCommon(models.AbstractModel):
"""Abstract Report Statement for use in other models""" """Abstract Report Statement for use in other models"""
_name = 'statement.common'
_description = 'Statement Reports Common'
_name = "statement.common"
_description = "Statement Reports Common"
def _get_invoice_address(self, part): def _get_invoice_address(self, part):
inv_addr_id = part.address_get(['invoice']).get('invoice', part.id)
inv_addr_id = part.address_get(["invoice"]).get("invoice", part.id)
return self.env["res.partner"].browse(inv_addr_id) return self.env["res.partner"].browse(inv_addr_id)
def _format_date_to_partner_lang( def _format_date_to_partner_lang(
self,
date,
date_format=DEFAULT_SERVER_DATE_FORMAT
self, date, date_format=DEFAULT_SERVER_DATE_FORMAT
): ):
if isinstance(date, str): if isinstance(date, str):
date = datetime.strptime(date, DEFAULT_SERVER_DATE_FORMAT) date = datetime.strptime(date, DEFAULT_SERVER_DATE_FORMAT)
return date.strftime(date_format) if date else ''
return date.strftime(date_format) if date else ""
def _get_account_display_lines(self, company_id, partner_ids, date_start,
date_end, account_type):
def _get_account_display_lines(
self, company_id, partner_ids, date_start, date_end, account_type
):
raise NotImplementedError raise NotImplementedError
def _get_account_initial_balance(self, company_id, partner_ids,
date_start, account_type):
def _get_account_initial_balance(
self, company_id, partner_ids, date_start, account_type
):
return {} return {}
def _show_buckets_sql_q1(self, partners, date_end, account_type): def _show_buckets_sql_q1(self, partners, date_end, account_type):
return str(self._cr.mogrify("""
return str(
self._cr.mogrify(
"""
SELECT l.partner_id, l.currency_id, l.company_id, l.move_id, SELECT l.partner_id, l.currency_id, l.company_id, l.move_id,
CASE WHEN l.balance > 0.0 CASE WHEN l.balance > 0.0
THEN l.balance - sum(coalesce(pd.amount, 0.0)) THEN l.balance - sum(coalesce(pd.amount, 0.0))
@ -76,11 +79,16 @@ class ReportStatementCommon(models.AbstractModel):
GROUP BY l.partner_id, l.currency_id, l.date, l.date_maturity, GROUP BY l.partner_id, l.currency_id, l.date, l.date_maturity,
l.amount_currency, l.balance, l.move_id, l.amount_currency, l.balance, l.move_id,
l.company_id, l.id l.company_id, l.id
""", locals()), "utf-8")
""",
locals(),
),
"utf-8",
)
def _show_buckets_sql_q2(self, date_end, minus_30, minus_60, minus_90,
minus_120):
return str(self._cr.mogrify("""
def _show_buckets_sql_q2(self, date_end, minus_30, minus_60, minus_90, minus_120):
return str(
self._cr.mogrify(
"""
SELECT partner_id, currency_id, date_maturity, open_due, SELECT partner_id, currency_id, date_maturity, open_due,
open_due_currency, move_id, company_id, open_due_currency, move_id, company_id,
CASE CASE
@ -146,17 +154,27 @@ class ReportStatementCommon(models.AbstractModel):
FROM Q1 FROM Q1
GROUP BY partner_id, currency_id, date_maturity, open_due, GROUP BY partner_id, currency_id, date_maturity, open_due,
open_due_currency, move_id, company_id open_due_currency, move_id, company_id
""", locals()), "utf-8")
""",
locals(),
),
"utf-8",
)
def _show_buckets_sql_q3(self, company_id): def _show_buckets_sql_q3(self, company_id):
return str(self._cr.mogrify("""
return str(
self._cr.mogrify(
"""
SELECT Q2.partner_id, current, b_1_30, b_30_60, b_60_90, b_90_120, SELECT Q2.partner_id, current, b_1_30, b_30_60, b_60_90, b_90_120,
b_over_120, b_over_120,
COALESCE(Q2.currency_id, c.currency_id) AS currency_id COALESCE(Q2.currency_id, c.currency_id) AS currency_id
FROM Q2 FROM Q2
JOIN res_company c ON (c.id = Q2.company_id) JOIN res_company c ON (c.id = Q2.company_id)
WHERE c.id = %(company_id)s WHERE c.id = %(company_id)s
""", locals()), "utf-8")
""",
locals(),
),
"utf-8",
)
def _show_buckets_sql_q4(self): def _show_buckets_sql_q4(self):
return """ return """
@ -172,41 +190,36 @@ class ReportStatementCommon(models.AbstractModel):
def _get_bucket_dates(self, date_end, aging_type): def _get_bucket_dates(self, date_end, aging_type):
return getattr( return getattr(
self, '_get_bucket_dates_%s' % aging_type,
self._get_bucket_dates_days
self, "_get_bucket_dates_%s" % aging_type, self._get_bucket_dates_days
)(date_end) )(date_end)
def _get_bucket_dates_days(self, date_end): def _get_bucket_dates_days(self, date_end):
return { return {
'date_end': date_end,
'minus_30': date_end - timedelta(days=30),
'minus_60': date_end - timedelta(days=60),
'minus_90': date_end - timedelta(days=90),
'minus_120': date_end - timedelta(days=120),
"date_end": date_end,
"minus_30": date_end - timedelta(days=30),
"minus_60": date_end - timedelta(days=60),
"minus_90": date_end - timedelta(days=90),
"minus_120": date_end - timedelta(days=120),
} }
def _get_bucket_dates_months(self, date_end): def _get_bucket_dates_months(self, date_end):
res = {} res = {}
d = date_end d = date_end
for k in (
"date_end",
"minus_30",
"minus_60",
"minus_90",
"minus_120",
):
for k in ("date_end", "minus_30", "minus_60", "minus_90", "minus_120"):
res[k] = d res[k] = d
d = d.replace(day=1) - timedelta(days=1) d = d.replace(day=1) - timedelta(days=1)
return res return res
def _get_account_show_buckets(self, company_id, partner_ids, date_end,
account_type, aging_type):
def _get_account_show_buckets(
self, company_id, partner_ids, date_end, account_type, aging_type
):
buckets = dict(map(lambda x: (x, []), partner_ids)) buckets = dict(map(lambda x: (x, []), partner_ids))
partners = tuple(partner_ids) partners = tuple(partner_ids)
full_dates = self._get_bucket_dates(date_end, aging_type) full_dates = self._get_bucket_dates(date_end, aging_type)
# pylint: disable=E8103 # pylint: disable=E8103
# All input queries are properly escaped - false positive # All input queries are properly escaped - false positive
self.env.cr.execute("""
self.env.cr.execute(
"""
WITH Q1 AS (%s), WITH Q1 AS (%s),
Q2 AS (%s), Q2 AS (%s),
Q3 AS (%s), Q3 AS (%s),
@ -217,63 +230,63 @@ class ReportStatementCommon(models.AbstractModel):
AS balance AS balance
FROM Q4 FROM Q4
GROUP BY partner_id, currency_id, current, b_1_30, b_30_60, GROUP BY partner_id, currency_id, current, b_1_30, b_30_60,
b_60_90, b_90_120, b_over_120""" % (
self._show_buckets_sql_q1(partners, date_end, account_type),
self._show_buckets_sql_q2(
full_dates['date_end'],
full_dates['minus_30'],
full_dates['minus_60'],
full_dates['minus_90'],
full_dates['minus_120']),
self._show_buckets_sql_q3(company_id),
self._show_buckets_sql_q4()))
b_60_90, b_90_120, b_over_120"""
% (
self._show_buckets_sql_q1(partners, date_end, account_type),
self._show_buckets_sql_q2(
full_dates["date_end"],
full_dates["minus_30"],
full_dates["minus_60"],
full_dates["minus_90"],
full_dates["minus_120"],
),
self._show_buckets_sql_q3(company_id),
self._show_buckets_sql_q4(),
)
)
for row in self.env.cr.dictfetchall(): for row in self.env.cr.dictfetchall():
buckets[row.pop('partner_id')].append(row)
buckets[row.pop("partner_id")].append(row)
return buckets return buckets
def _get_bucket_labels(self, date_end, aging_type): def _get_bucket_labels(self, date_end, aging_type):
return getattr( return getattr(
self, '_get_bucket_labels_%s' % aging_type,
self._get_bucket_dates_days
self, "_get_bucket_labels_%s" % aging_type, self._get_bucket_dates_days
)(date_end) )(date_end)
def _get_bucket_labels_days(self, date_end): def _get_bucket_labels_days(self, date_end):
return [ return [
_('Current'),
_('1 - 30 Days'),
_('31 - 60 Days'),
_('61 - 90 Days'),
_('91 - 120 Days'),
_('121 Days +'),
_('Total'),
_("Current"),
_("1 - 30 Days"),
_("31 - 60 Days"),
_("61 - 90 Days"),
_("91 - 120 Days"),
_("121 Days +"),
_("Total"),
] ]
def _get_bucket_labels_months(self, date_end): def _get_bucket_labels_months(self, date_end):
return [ return [
_('Current'),
_('1 Month'),
_('2 Months'),
_('3 Months'),
_('4 Months'),
_('Older'),
_('Total'),
_("Current"),
_("1 Month"),
_("2 Months"),
_("3 Months"),
_("4 Months"),
_("Older"),
_("Total"),
] ]
def _get_line_currency_defaults(self, currency_id, currencies,
balance_forward):
def _get_line_currency_defaults(self, currency_id, currencies, balance_forward):
if currency_id not in currencies: if currency_id not in currencies:
# This will only happen if currency is inactive # This will only happen if currency is inactive
currencies[currency_id] = (
self.env['res.currency'].browse(currency_id)
)
currencies[currency_id] = self.env["res.currency"].browse(currency_id)
return ( return (
{ {
'lines': [],
'buckets': [],
'balance_forward': balance_forward,
'amount_due': balance_forward,
"lines": [],
"buckets": [],
"balance_forward": balance_forward,
"amount_due": balance_forward,
}, },
currencies
currencies,
) )
@api.multi @api.multi
@ -298,45 +311,48 @@ class ReportStatementCommon(models.AbstractModel):
} }
} }
""" """
company_id = data['company_id']
partner_ids = data['partner_ids']
date_start = data.get('date_start')
company_id = data["company_id"]
partner_ids = data["partner_ids"]
date_start = data.get("date_start")
if date_start and isinstance(date_start, str): if date_start and isinstance(date_start, str):
date_start = datetime.strptime( date_start = datetime.strptime(
date_start, DEFAULT_SERVER_DATE_FORMAT date_start, DEFAULT_SERVER_DATE_FORMAT
).date() ).date()
date_end = data['date_end']
date_end = data["date_end"]
if isinstance(date_end, str): if isinstance(date_end, str):
date_end = datetime.strptime(
date_end, DEFAULT_SERVER_DATE_FORMAT
).date()
account_type = data['account_type']
aging_type = data['aging_type']
date_end = datetime.strptime(date_end, DEFAULT_SERVER_DATE_FORMAT).date()
account_type = data["account_type"]
aging_type = data["aging_type"]
today = fields.Date.today() today = fields.Date.today()
amount_field = data.get('amount_field', 'amount')
amount_field = data.get("amount_field", "amount")
# There should be relatively few of these, so to speed performance # There should be relatively few of these, so to speed performance
# we cache them - default needed if partner lang not set # we cache them - default needed if partner lang not set
self._cr.execute("""
self._cr.execute(
"""
SELECT p.id, l.date_format SELECT p.id, l.date_format
FROM res_partner p LEFT JOIN res_lang l ON p.lang=l.code FROM res_partner p LEFT JOIN res_lang l ON p.lang=l.code
WHERE p.id IN %(partner_ids)s WHERE p.id IN %(partner_ids)s
""", {"partner_ids": tuple(partner_ids)})
""",
{"partner_ids": tuple(partner_ids)},
)
date_formats = {r[0]: r[1] for r in self._cr.fetchall()} date_formats = {r[0]: r[1] for r in self._cr.fetchall()}
default_fmt = self.env["res.lang"]._lang_get(
self.env.user.lang).date_format
currencies = {x.id: x for x in self.env['res.currency'].search([])}
default_fmt = self.env["res.lang"]._lang_get(self.env.user.lang).date_format
currencies = {x.id: x for x in self.env["res.currency"].search([])}
res = {} res = {}
# get base data # get base data
lines = self._get_account_display_lines( lines = self._get_account_display_lines(
company_id, partner_ids, date_start, date_end, account_type)
company_id, partner_ids, date_start, date_end, account_type
)
balances_forward = self._get_account_initial_balance( balances_forward = self._get_account_initial_balance(
company_id, partner_ids, date_start, account_type)
company_id, partner_ids, date_start, account_type
)
if data['show_aging_buckets']:
if data["show_aging_buckets"]:
buckets = self._get_account_show_buckets( buckets = self._get_account_show_buckets(
company_id, partner_ids, date_end, account_type, aging_type)
company_id, partner_ids, date_end, account_type, aging_type
)
bucket_labels = self._get_bucket_labels(date_end, aging_type) bucket_labels = self._get_bucket_labels(date_end, aging_type)
else: else:
bucket_labels = {} bucket_labels = {}
@ -346,69 +362,65 @@ class ReportStatementCommon(models.AbstractModel):
partners_to_remove = set() partners_to_remove = set()
for partner_id in partner_ids: for partner_id in partner_ids:
res[partner_id] = { res[partner_id] = {
'today': format_date(
today,
date_formats.get(partner_id, default_fmt)
),
'start': format_date(
date_start,
date_formats.get(partner_id, default_fmt)
),
'end': format_date(
date_end,
date_formats.get(partner_id, default_fmt)
"today": format_date(today, date_formats.get(partner_id, default_fmt)),
"start": format_date(
date_start, date_formats.get(partner_id, default_fmt)
), ),
'currencies': {},
"end": format_date(date_end, date_formats.get(partner_id, default_fmt)),
"currencies": {},
} }
currency_dict = res[partner_id]['currencies']
currency_dict = res[partner_id]["currencies"]
for line in balances_forward.get(partner_id, []): for line in balances_forward.get(partner_id, []):
currency_dict[line['currency_id']], currencies = (
self._get_line_currency_defaults(
line['currency_id'], currencies, line['balance'])
(
currency_dict[line["currency_id"]],
currencies,
) = self._get_line_currency_defaults(
line["currency_id"], currencies, line["balance"]
) )
for line in lines[partner_id]: for line in lines[partner_id]:
if line['currency_id'] not in currency_dict:
currency_dict[line['currency_id']], currencies = (
self._get_line_currency_defaults(
line['currency_id'], currencies, 0.0)
if line["currency_id"] not in currency_dict:
(
currency_dict[line["currency_id"]],
currencies,
) = self._get_line_currency_defaults(
line["currency_id"], currencies, 0.0
) )
line_currency = currency_dict[line['currency_id']]
if not line['blocked']:
line_currency['amount_due'] += line[amount_field]
line['balance'] = line_currency['amount_due']
line['date'] = format_date(
line['date'], date_formats.get(partner_id, default_fmt)
line_currency = currency_dict[line["currency_id"]]
if not line["blocked"]:
line_currency["amount_due"] += line[amount_field]
line["balance"] = line_currency["amount_due"]
line["date"] = format_date(
line["date"], date_formats.get(partner_id, default_fmt)
) )
line['date_maturity'] = format_date(
line['date_maturity'],
date_formats.get(partner_id, default_fmt)
line["date_maturity"] = format_date(
line["date_maturity"], date_formats.get(partner_id, default_fmt)
) )
line_currency['lines'].append(line)
line_currency["lines"].append(line)
if data['show_aging_buckets']:
if data["show_aging_buckets"]:
for line in buckets[partner_id]: for line in buckets[partner_id]:
if line['currency_id'] not in currency_dict:
currency_dict[line['currency_id']], currencies = (
self._get_line_currency_defaults(
line['currency_id'], currencies, 0.0)
if line["currency_id"] not in currency_dict:
(
currency_dict[line["currency_id"]],
currencies,
) = self._get_line_currency_defaults(
line["currency_id"], currencies, 0.0
) )
line_currency = currency_dict[line['currency_id']]
line_currency['buckets'] = line
line_currency = currency_dict[line["currency_id"]]
line_currency["buckets"] = line
if len(partner_ids) > 1: if len(partner_ids) > 1:
values = currency_dict.values() values = currency_dict.values()
if not any(
[v['lines'] or v['balance_forward'] for v in values]
):
if not any([v["lines"] or v["balance_forward"] for v in values]):
if data["filter_non_due_partners"]: if data["filter_non_due_partners"]:
partners_to_remove.add(partner_id) partners_to_remove.add(partner_id)
continue continue
else: else:
res[partner_id]['no_entries'] = True
res[partner_id]["no_entries"] = True
if data["filter_negative_balances"]: if data["filter_negative_balances"]:
if not all([v['amount_due'] >= 0.0 for v in values]):
if not all([v["amount_due"] >= 0.0 for v in values]):
partners_to_remove.add(partner_id) partners_to_remove.add(partner_id)
for partner in partners_to_remove: for partner in partners_to_remove:
@ -416,13 +428,13 @@ class ReportStatementCommon(models.AbstractModel):
partner_ids.remove(partner) partner_ids.remove(partner)
return { return {
'doc_ids': partner_ids,
'doc_model': 'res.partner',
'docs': self.env['res.partner'].browse(partner_ids),
'data': res,
'company': self.env['res.company'].browse(company_id),
'Currencies': currencies,
'account_type': account_type,
'bucket_labels': bucket_labels,
'get_inv_addr': self._get_invoice_address,
"doc_ids": partner_ids,
"doc_model": "res.partner",
"docs": self.env["res.partner"].browse(partner_ids),
"data": res,
"company": self.env["res.company"].browse(company_id),
"Currencies": currencies,
"account_type": account_type,
"bucket_labels": bucket_labels,
"get_inv_addr": self._get_invoice_address,
} }

9
partner_statement/security/statement_security.xml

@ -1,14 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<record id="group_activity_statement" model="res.groups"> <record id="group_activity_statement" model="res.groups">
<field name="name">Use activity statements</field> <field name="name">Use activity statements</field>
<field name="category_id" ref="base.module_category_hidden"/>
<field name="category_id" ref="base.module_category_hidden" />
</record> </record>
<record id="group_outstanding_statement" model="res.groups"> <record id="group_outstanding_statement" model="res.groups">
<field name="name">Use outstanding statements</field> <field name="name">Use outstanding statements</field>
<field name="category_id" ref="base.module_category_hidden"/>
<field name="category_id" ref="base.module_category_hidden" />
</record> </record>
</odoo> </odoo>

6
partner_statement/static/src/scss/layout_statement.scss

@ -1,16 +1,16 @@
.table-statement { .table-statement {
.amount { .amount {
text-align: right !important; text-align: right !important;
width: 14% //spread 7 columns evenly
width: 14%; //spread 7 columns evenly
} }
thead { thead {
border-bottom: solid; // required for clean layout border-bottom: solid; // required for clean layout
tr th:first-child { tr th:first-child {
width: auto !important; // required for clean layout width: auto !important; // required for clean layout
}
}
tr th:last-child { tr th:last-child {
width: 16% !important; // required for boxed layout width: 16% !important; // required for boxed layout
}
}
} }
} }

96
partner_statement/tests/test_activity_statement.py

@ -3,108 +3,112 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from datetime import date from datetime import date
from odoo.tests.common import TransactionCase
from odoo import fields from odoo import fields
from odoo.tests.common import TransactionCase
class TestActivityStatement(TransactionCase): class TestActivityStatement(TransactionCase):
""" """
Tests for Activity Statement. Tests for Activity Statement.
""" """
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.res_users_model = self.env['res.users']
self.company = self.env.ref('base.main_company')
self.partner1 = self.env.ref('base.res_partner_1')
self.partner2 = self.env.ref('base.res_partner_2')
self.g_account_user = self.env.ref('account.group_account_user')
self.res_users_model = self.env["res.users"]
self.company = self.env.ref("base.main_company")
self.partner1 = self.env.ref("base.res_partner_1")
self.partner2 = self.env.ref("base.res_partner_2")
self.g_account_user = self.env.ref("account.group_account_user")
self.user = self._create_user('user_1', [self.g_account_user],
self.company).id
self.user = self._create_user("user_1", [self.g_account_user], self.company).id
self.statement_model = \
self.env['report.partner_statement.activity_statement']
self.wiz = self.env['activity.statement.wizard']
self.report_name = 'partner_statement.activity_statement'
self.report_title = 'Activity Statement'
self.statement_model = self.env["report.partner_statement.activity_statement"]
self.wiz = self.env["activity.statement.wizard"]
self.report_name = "partner_statement.activity_statement"
self.report_title = "Activity Statement"
self.today = fields.Date.context_today(self.wiz) self.today = fields.Date.context_today(self.wiz)
def _create_user(self, login, groups, company): def _create_user(self, login, groups, company):
group_ids = [group.id for group in groups] group_ids = [group.id for group in groups]
user = self.res_users_model.create({
'name': login,
'login': login,
'email': 'example@yourcompany.com',
'company_id': company.id,
'company_ids': [(4, company.id)],
'groups_id': [(6, 0, group_ids)]
})
user = self.res_users_model.create(
{
"name": login,
"login": login,
"email": "example@yourcompany.com",
"company_id": company.id,
"company_ids": [(4, company.id)],
"groups_id": [(6, 0, group_ids)],
}
)
return user return user
def test_customer_activity_statement(self): def test_customer_activity_statement(self):
wiz_id = self.wiz.with_context( wiz_id = self.wiz.with_context(
active_ids=[self.partner1.id, self.partner2.id],
active_ids=[self.partner1.id, self.partner2.id]
).create({}) ).create({})
wiz_id.aging_type = 'months'
wiz_id.aging_type = "months"
wiz_id.show_aging_buckets = False wiz_id.show_aging_buckets = False
statement = wiz_id.button_export_pdf() statement = wiz_id.button_export_pdf()
self.assertDictContainsSubset( self.assertDictContainsSubset(
{ {
'type': 'ir.actions.report',
'report_name': self.report_name,
'report_type': 'qweb-pdf',
"type": "ir.actions.report",
"report_name": self.report_name,
"report_type": "qweb-pdf",
}, },
statement, statement,
'There was an error and the PDF report was not generated.'
"There was an error and the PDF report was not generated.",
) )
data = wiz_id._prepare_statement() data = wiz_id._prepare_statement()
docids = data['partner_ids']
docids = data["partner_ids"]
report = self.statement_model._get_report_values(docids, data) report = self.statement_model._get_report_values(docids, data)
self.assertIsInstance(report, dict,
"There was an error while compiling the report.")
self.assertIn("bucket_labels", report,
"There was an error while compiling the report.")
self.assertIsInstance(
report, dict, "There was an error while compiling the report."
)
self.assertIn(
"bucket_labels", report, "There was an error while compiling the report."
)
def test_customer_activity_report_no_wizard(self): def test_customer_activity_report_no_wizard(self):
docids = [self.partner1.id, self.partner2.id] docids = [self.partner1.id, self.partner2.id]
report = self.statement_model._get_report_values(docids, False) report = self.statement_model._get_report_values(docids, False)
self.assertIsInstance(report, dict,
"There was an error while compiling the report.")
self.assertIn("bucket_labels", report,
"There was an error while compiling the report.")
self.assertIsInstance(
report, dict, "There was an error while compiling the report."
)
self.assertIn(
"bucket_labels", report, "There was an error while compiling the report."
)
def test_date_formatting(self): def test_date_formatting(self):
date_fmt = '%d/%m/%Y'
date_fmt = "%d/%m/%Y"
test_date = date(2018, 9, 30) test_date = date(2018, 9, 30)
res = self.statement_model._format_date_to_partner_lang(
test_date, date_fmt
)
self.assertEqual(res, '30/09/2018')
res = self.statement_model._format_date_to_partner_lang(test_date, date_fmt)
self.assertEqual(res, "30/09/2018")
test_date_string = '2018-09-30'
test_date_string = "2018-09-30"
res = self.statement_model._format_date_to_partner_lang( res = self.statement_model._format_date_to_partner_lang(
test_date_string, date_fmt test_date_string, date_fmt
) )
self.assertEqual(res, '30/09/2018')
self.assertEqual(res, "30/09/2018")
def test_onchange_aging_type(self): def test_onchange_aging_type(self):
"""Test that partner data is filled accodingly""" """Test that partner data is filled accodingly"""
wiz_id = self.wiz.with_context( wiz_id = self.wiz.with_context(
active_ids=[self.partner1.id, self.partner2.id],
active_ids=[self.partner1.id, self.partner2.id]
).new() ).new()
wiz_id.aging_type = 'months'
wiz_id.aging_type = "months"
wiz_id.onchange_aging_type() wiz_id.onchange_aging_type()
self.assertEqual(wiz_id.date_end.month, wiz_id.date_start.month) self.assertEqual(wiz_id.date_end.month, wiz_id.date_start.month)
self.assertTrue(wiz_id.date_end.day > wiz_id.date_start.day) self.assertTrue(wiz_id.date_end.day > wiz_id.date_start.day)
self.assertTrue(wiz_id.date_end < self.today) self.assertTrue(wiz_id.date_end < self.today)
wiz_id.aging_type = 'days'
wiz_id.aging_type = "days"
wiz_id.onchange_aging_type() wiz_id.onchange_aging_type()
self.assertEqual((wiz_id.date_end - wiz_id.date_start).days, 30) self.assertEqual((wiz_id.date_end - wiz_id.date_start).days, 30)
self.assertTrue(wiz_id.date_end == self.today) self.assertTrue(wiz_id.date_end == self.today)

78
partner_statement/tests/test_outstanding_statement.py

@ -9,68 +9,74 @@ class TestOutstandingStatement(TransactionCase):
""" """
Tests for Outstanding Statement. Tests for Outstanding Statement.
""" """
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.res_users_model = self.env['res.users']
self.company = self.env.ref('base.main_company')
self.partner1 = self.env.ref('base.res_partner_2')
self.partner2 = self.env.ref('base.res_partner_3')
self.g_account_user = self.env.ref('account.group_account_user')
self.res_users_model = self.env["res.users"]
self.company = self.env.ref("base.main_company")
self.partner1 = self.env.ref("base.res_partner_2")
self.partner2 = self.env.ref("base.res_partner_3")
self.g_account_user = self.env.ref("account.group_account_user")
self.user = self._create_user('user_1', [self.g_account_user],
self.company).id
self.user = self._create_user("user_1", [self.g_account_user], self.company).id
self.statement_model = \
self.env['report.partner_statement.outstanding_statement']
self.wiz = self.env['outstanding.statement.wizard']
self.report_name = 'partner_statement.outstanding_statement'
self.report_title = 'Outstanding Statement'
self.statement_model = self.env[
"report.partner_statement.outstanding_statement"
]
self.wiz = self.env["outstanding.statement.wizard"]
self.report_name = "partner_statement.outstanding_statement"
self.report_title = "Outstanding Statement"
def _create_user(self, login, groups, company): def _create_user(self, login, groups, company):
group_ids = [group.id for group in groups] group_ids = [group.id for group in groups]
user = self.res_users_model.create({
'name': login,
'login': login,
'email': 'example@yourcompany.com',
'company_id': company.id,
'company_ids': [(4, company.id)],
'groups_id': [(6, 0, group_ids)]
})
user = self.res_users_model.create(
{
"name": login,
"login": login,
"email": "example@yourcompany.com",
"company_id": company.id,
"company_ids": [(4, company.id)],
"groups_id": [(6, 0, group_ids)],
}
)
return user return user
def test_customer_outstanding_statement(self): def test_customer_outstanding_statement(self):
wiz_id = self.wiz.with_context( wiz_id = self.wiz.with_context(
active_ids=[self.partner1.id, self.partner2.id],
active_ids=[self.partner1.id, self.partner2.id]
).create({}) ).create({})
wiz_id.aging_type = 'months'
wiz_id.aging_type = "months"
statement = wiz_id.button_export_pdf() statement = wiz_id.button_export_pdf()
self.assertDictContainsSubset( self.assertDictContainsSubset(
{ {
'type': 'ir.actions.report',
'report_name': self.report_name,
'report_type': 'qweb-pdf',
"type": "ir.actions.report",
"report_name": self.report_name,
"report_type": "qweb-pdf",
}, },
statement, statement,
'There was an error and the PDF report was not generated.'
"There was an error and the PDF report was not generated.",
) )
data = wiz_id._prepare_statement() data = wiz_id._prepare_statement()
docids = data['partner_ids']
docids = data["partner_ids"]
report = self.statement_model._get_report_values(docids, data) report = self.statement_model._get_report_values(docids, data)
self.assertIsInstance(report,
dict,
"There was an error while compiling the report.")
self.assertIn("bucket_labels", report,
"There was an error while compiling the report.")
self.assertIsInstance(
report, dict, "There was an error while compiling the report."
)
self.assertIn(
"bucket_labels", report, "There was an error while compiling the report."
)
def test_customer_outstanding_report_no_wizard(self): def test_customer_outstanding_report_no_wizard(self):
docids = [self.partner1.id] docids = [self.partner1.id]
report = self.statement_model._get_report_values(docids, False) report = self.statement_model._get_report_values(docids, False)
self.assertIsInstance(report, dict,
"There was an error while compiling the report.")
self.assertIn("bucket_labels", report,
"There was an error while compiling the report.")
self.assertIsInstance(
report, dict, "There was an error while compiling the report."
)
self.assertIn(
"bucket_labels", report, "There was an error while compiling the report."
)

31
partner_statement/tests/test_res_config_settings.py

@ -4,10 +4,9 @@ from odoo.tests.common import TransactionCase
class TestResConfigSettings(TransactionCase): class TestResConfigSettings(TransactionCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.config = self.env['res.config.settings']
self.config = self.env["res.config.settings"]
self.cr.execute( self.cr.execute(
"SELECT uid FROM res_groups_users_rel " "SELECT uid FROM res_groups_users_rel "
"WHERE gid IN (SELECT res_id FROM ir_model_data " "WHERE gid IN (SELECT res_id FROM ir_model_data "
@ -15,22 +14,22 @@ class TestResConfigSettings(TransactionCase):
"ORDER BY uid DESC LIMIT 1" "ORDER BY uid DESC LIMIT 1"
) )
self.account_user = self.cr.fetchone()[0] self.account_user = self.cr.fetchone()[0]
self.user_obj = self.env['res.users'].sudo(self.account_user)
self.user_obj = self.env["res.users"].sudo(self.account_user)
def test_groups(self): def test_groups(self):
conf = self.config.create({
"default_aging_type": "months",
"group_activity_statement": True,
"group_outstanding_statement": False,
})
conf.set_values()
self.assertFalse(self.user_obj._has_group(
'partner_statement.group_outstanding_statement')
conf = self.config.create(
{
"default_aging_type": "months",
"group_activity_statement": True,
"group_outstanding_statement": False,
}
) )
self.assertTrue(self.user_obj._has_group(
'partner_statement.group_activity_statement')
conf.set_values()
self.assertFalse(
self.user_obj._has_group("partner_statement.group_outstanding_statement")
) )
res = self.env['ir.default'].get(
'activity.statement.wizard', 'aging_type'
self.assertTrue(
self.user_obj._has_group("partner_statement.group_activity_statement")
) )
self.assertEquals(res, 'months')
res = self.env["ir.default"].get("activity.statement.wizard", "aging_type")
self.assertEquals(res, "months")

228
partner_statement/views/activity_statement.xml

@ -1,140 +1,172 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2018 Eficent Business and IT Consulting Services S.L. <!-- Copyright 2018 Eficent Business and IT Consulting Services S.L.
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo> <odoo>
<template id="partner_statement.activity_statement_document"> <template id="partner_statement.activity_statement_document">
<t t-call="web.external_layout"> <t t-call="web.external_layout">
<t t-set="o" t-value="o.with_context({'lang': lang})"/>
<t t-set="o" t-value="o.with_context({'lang': lang})" />
<t t-set="address"> <t t-set="address">
<address t-esc="get_inv_addr(o)" t-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": True}' />
<div t-if="o.vat" class="mt16"><t t-esc="company.country_id.vat_label or 'Tax ID'"/>: <span t-field="o.vat"/></div>
<address
t-esc="get_inv_addr(o)"
t-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": True}'
/>
<div t-if="o.vat" class="mt16"><t
t-esc="company.country_id.vat_label or 'Tax ID'"
/>: <span t-field="o.vat" /></div>
</t> </t>
<div class="page"> <div class="page">
<h2 >Statement of Account</h2>
<h2>Statement of Account</h2>
<div id="informations" class="row mt32 mb32"> <div id="informations" class="row mt32 mb32">
<div class="col-3 bm-2"> <div class="col-3 bm-2">
<strong>Date:</strong> <strong>Date:</strong>
<p t-esc="d['today']" class="m-0"/>
<p t-esc="d['today']" class="m-0" />
</div> </div>
<div t-if="o.ref" class="col-3 bm-2"> <div t-if="o.ref" class="col-3 bm-2">
<strong>Partner Code:</strong> <strong>Partner Code:</strong>
<p t-field="o.ref" class="m-0" /> <p t-field="o.ref" class="m-0" />
</div> </div>
</div> </div>
<t t-if="d['currencies']">
<br/>
<t t-foreach="d['currencies'].items()" t-as="currency">
<t t-set="display_currency" t-value="Currencies[currency[0]]" />
<t t-set="currency" t-value="currency[1]" />
<p>
<span t-if="account_type == 'payable'">Supplier </span>Statement between <span t-esc="d['start']" /> and <span t-esc="d['end']" /> in <span t-esc="display_currency.name"/>
<t t-if="d['currencies']">
<br />
<t t-foreach="d['currencies'].items()" t-as="currency">
<t t-set="display_currency" t-value="Currencies[currency[0]]" />
<t t-set="currency" t-value="currency[1]" />
<p>
<span
t-if="account_type == 'payable'"
>Supplier </span>Statement between <span
t-esc="d['start']"
/> and <span t-esc="d['end']" /> in <span
t-esc="display_currency.name"
/>
</p> </p>
<table class="table table-condensed table-statement">
<thead>
<tr>
<th>Reference number</th>
<th>Date</th>
<th>Description</th>
<th class="amount">Amount</th>
<th class="amount">Balance</th>
</tr>
</thead>
<tbody>
<tr>
<td />
<td>
<span t-esc="d['start']"/>
</td>
<td>
<table class="table table-condensed table-statement">
<thead>
<tr>
<th>Reference number</th>
<th>Date</th>
<th>Description</th>
<th class="amount">Amount</th>
<th class="amount">Balance</th>
</tr>
</thead>
<tbody>
<tr>
<td />
<td>
<span t-esc="d['start']" />
</td>
<td>
Balance Forward Balance Forward
</td> </td>
<td />
<td class="amount">
<span t-esc="currency['balance_forward']" t-options="{'widget': 'monetary', 'display_currency': display_currency}"/>
</td>
</tr>
<tr t-foreach="currency['lines']" t-as="line" t-att-class="'statement-blocked' if line['blocked'] else ''">
<td>
<span t-esc="line['move_id']"/>
</td>
<td>
<span t-esc="line['date']"/>
</td>
<td>
<t t-if="line['name'] != '/'">
<t t-if="not line['ref']">
<span t-esc="line['name']"/>
<td />
<td class="amount">
<span
t-esc="currency['balance_forward']"
t-options="{'widget': 'monetary', 'display_currency': display_currency}"
/>
</td>
</tr>
<tr
t-foreach="currency['lines']"
t-as="line"
t-att-class="'statement-blocked' if line['blocked'] else ''"
>
<td>
<span t-esc="line['move_id']" />
</td>
<td>
<span t-esc="line['date']" />
</td>
<td>
<t t-if="line['name'] != '/'">
<t t-if="not line['ref']">
<span t-esc="line['name']" />
</t>
<t t-if="line['ref'] and line['name']">
<t
t-if="line['name'] not in line['ref']"
>
<span t-esc="line['name']" />
</t> </t>
<t t-if="line['ref'] and line['name']">
<t t-if="line['name'] not in line['ref']">
<span t-esc="line['name']"/>
</t>
<t t-if="line['ref'] not in line['name']">
<span t-esc="line['ref']"/>
</t>
<t t-if="line['name'] == line['ref']">
<span t-esc="line['name']"/>
</t>
<t
t-if="line['ref'] not in line['name']"
>
<span t-esc="line['ref']" />
</t>
<t t-if="line['name'] == line['ref']">
<span t-esc="line['name']" />
</t> </t>
</t> </t>
<t t-if="line['name'] == '/'">
<span t-if="line['ref'] == 'Payment'">Payment</span>
<span t-else="" t-esc="line['ref']"/>
</t>
</td>
<td class="amount">
<span t-esc="line['amount']" t-options="{'widget': 'monetary', 'display_currency': display_currency}"/>
</td>
<td class="amount">
<span t-esc="line['balance']" t-options="{'widget': 'monetary', 'display_currency': display_currency}"/>
</td>
</tr>
<tr>
<td />
<td>
<span t-esc="d['end']"/>
</td>
<td>
</t>
<t t-if="line['name'] == '/'">
<span
t-if="line['ref'] == 'Payment'"
>Payment</span>
<span t-else="" t-esc="line['ref']" />
</t>
</td>
<td class="amount">
<span
t-esc="line['amount']"
t-options="{'widget': 'monetary', 'display_currency': display_currency}"
/>
</td>
<td class="amount">
<span
t-esc="line['balance']"
t-options="{'widget': 'monetary', 'display_currency': display_currency}"
/>
</td>
</tr>
<tr>
<td />
<td>
<span t-esc="d['end']" />
</td>
<td>
Ending Balance Ending Balance
</td> </td>
<td />
<td class="amount">
<span t-esc="currency['amount_due']" t-options="{'widget': 'monetary', 'display_currency': display_currency}"/>
</td>
</tr>
</tbody>
</table>
<t t-call="partner_statement.aging_buckets" t-if="currency['buckets']" />
</t>
<td />
<td class="amount">
<span
t-esc="currency['amount_due']"
t-options="{'widget': 'monetary', 'display_currency': display_currency}"
/>
</td>
</tr>
</tbody>
</table>
<t
t-call="partner_statement.aging_buckets"
t-if="currency['buckets']"
/>
</t> </t>
<p t-if="d.get('no_entries')">
<strong>The partner doesn't have due entries.</strong>
</p>
</div>
</t>
<p t-if="d.get('no_entries')">
<strong>The partner doesn't have due entries.</strong>
</p>
</div>
</t> </t>
</template> </template>
<template id="activity_statement"> <template id="activity_statement">
<t t-call="web.html_container"> <t t-call="web.html_container">
<t t-foreach="docs" t-as="o"> <t t-foreach="docs" t-as="o">
<t t-set="d" t-value="data.get(o.id)" /> <t t-set="d" t-value="data.get(o.id)" />
<t t-call="partner_statement.activity_statement_document" t-lang="o.lang"/>
<t
t-call="partner_statement.activity_statement_document"
t-lang="o.lang"
/>
</t> </t>
</t> </t>
</template> </template>
<report id="action_print_activity_statement"
<report
id="action_print_activity_statement"
model="res.partner" model="res.partner"
report_type="qweb-pdf" report_type="qweb-pdf"
menu="False" menu="False"
string="Activity Statement" string="Activity Statement"
name="partner_statement.activity_statement" name="partner_statement.activity_statement"
file="partner_statement.activity_statement" file="partner_statement.activity_statement"
/>
/>
</odoo> </odoo>

43
partner_statement/views/aging_buckets.xml

@ -1,11 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2018 Eficent Business and IT Consulting Services S.L. <!-- Copyright 2018 Eficent Business and IT Consulting Services S.L.
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo> <odoo>
<template id="aging_buckets"> <template id="aging_buckets">
<p> <p>
Aging Report at <span t-esc="d['end']" /> in <span t-esc="display_currency.name"/>:
Aging Report at <span t-esc="d['end']" /> in <span
t-esc="display_currency.name"
/>:
</p> </p>
<table class="table table-sm table-statement"> <table class="table table-sm table-statement">
<thead> <thead>
@ -21,29 +22,49 @@
<tbody> <tbody>
<tr> <tr>
<td class="amount"> <td class="amount">
<span t-esc="buckets.get('current', 0.0)" t-options="{'widget': 'monetary', 'display_currency': display_currency}"/>
<span
t-esc="buckets.get('current', 0.0)"
t-options="{'widget': 'monetary', 'display_currency': display_currency}"
/>
</td> </td>
<td class="amount"> <td class="amount">
<span t-esc="buckets.get('b_1_30', 0.0)" t-options="{'widget': 'monetary', 'display_currency': display_currency}"/>
<span
t-esc="buckets.get('b_1_30', 0.0)"
t-options="{'widget': 'monetary', 'display_currency': display_currency}"
/>
</td> </td>
<td class="amount"> <td class="amount">
<span t-esc="buckets.get('b_30_60', 0.0)" t-options="{'widget': 'monetary', 'display_currency': display_currency}"/>
<span
t-esc="buckets.get('b_30_60', 0.0)"
t-options="{'widget': 'monetary', 'display_currency': display_currency}"
/>
</td> </td>
<td class="amount"> <td class="amount">
<span t-esc="buckets.get('b_60_90', 0.0)" t-options="{'widget': 'monetary', 'display_currency': display_currency}"/>
<span
t-esc="buckets.get('b_60_90', 0.0)"
t-options="{'widget': 'monetary', 'display_currency': display_currency}"
/>
</td> </td>
<td class="amount"> <td class="amount">
<span t-esc="buckets.get('b_90_120', 0.0)" t-options="{'widget': 'monetary', 'display_currency': display_currency}"/>
<span
t-esc="buckets.get('b_90_120', 0.0)"
t-options="{'widget': 'monetary', 'display_currency': display_currency}"
/>
</td> </td>
<td class="amount"> <td class="amount">
<span t-esc="buckets.get('b_over_120', 0.0)" t-options="{'widget': 'monetary', 'display_currency': display_currency}"/>
<span
t-esc="buckets.get('b_over_120', 0.0)"
t-options="{'widget': 'monetary', 'display_currency': display_currency}"
/>
</td> </td>
<td class="amount"> <td class="amount">
<span t-esc="buckets.get('balance', 0.0)" t-options="{'widget': 'monetary', 'display_currency': display_currency}"/>
<span
t-esc="buckets.get('balance', 0.0)"
t-options="{'widget': 'monetary', 'display_currency': display_currency}"
/>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</template> </template>
</odoo> </odoo>

16
partner_statement/views/assets.xml

@ -1,12 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2018 Eficent Business and IT Consulting Services S.L. <!-- Copyright 2018 Eficent Business and IT Consulting Services S.L.
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo> <odoo>
<template id="report_assets_common" name="oca_statements report assets" inherit_id="web.report_assets_common">
<template
id="report_assets_common"
name="oca_statements report assets"
inherit_id="web.report_assets_common"
>
<xpath expr="." position="inside"> <xpath expr="." position="inside">
<link rel="stylesheet" href="/partner_statement/static/src/scss/layout_statement.scss" type="text/scss" />
<link
rel="stylesheet"
href="/partner_statement/static/src/scss/layout_statement.scss"
type="text/scss"
/>
</xpath> </xpath>
</template> </template>
</odoo> </odoo>

221
partner_statement/views/outstanding_statement.xml

@ -1,136 +1,165 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2018 Eficent Business and IT Consulting Services S.L. <!-- Copyright 2018 Eficent Business and IT Consulting Services S.L.
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo> <odoo>
<template id="partner_statement.outstanding_statement_document"> <template id="partner_statement.outstanding_statement_document">
<t t-call="web.external_layout"> <t t-call="web.external_layout">
<t t-set="o" t-value="o.with_context({'lang': lang})"/>
<t t-set="o" t-value="o.with_context({'lang': lang})" />
<t t-set="address"> <t t-set="address">
<address t-esc="get_inv_addr(o)" t-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": True}' />
<div t-if="o.vat" class="mt16"><t t-esc="company.country_id.vat_label or 'Tax ID'"/>: <span t-field="o.vat"/></div>
<address
t-esc="get_inv_addr(o)"
t-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": True}'
/>
<div t-if="o.vat" class="mt16"><t
t-esc="company.country_id.vat_label or 'Tax ID'"
/>: <span t-field="o.vat" /></div>
</t> </t>
<div class="page"> <div class="page">
<div class="oe_structure"/>
<h2 >Statement of Account</h2>
<div class="oe_structure" />
<h2>Statement of Account</h2>
<div id="informations" class="row mt32 mb32"> <div id="informations" class="row mt32 mb32">
<div class="col-3 bm-2"> <div class="col-3 bm-2">
<strong>Date:</strong> <strong>Date:</strong>
<p t-esc="d['today']" class="m-0"/>
<p t-esc="d['today']" class="m-0" />
</div> </div>
<div t-if="o.ref" class="col-3 bm-2"> <div t-if="o.ref" class="col-3 bm-2">
<strong>Partner Code:</strong> <strong>Partner Code:</strong>
<p t-field="o.ref" class="m-0" /> <p t-field="o.ref" class="m-0" />
</div> </div>
</div> </div>
<t t-if="d['currencies']">
<br/>
<t t-foreach="d['currencies'].items()" t-as="currency">
<t t-set="display_currency" t-value="Currencies[currency[0]]" />
<t t-set="currency" t-value="currency[1]" />
<p>
<span t-esc="'' if account_type == 'receivable' else 'Supplier '"/>Statement up to <span t-esc="d['end']" /> in <span t-esc="display_currency.name"/>
<t t-if="d['currencies']">
<br />
<t t-foreach="d['currencies'].items()" t-as="currency">
<t t-set="display_currency" t-value="Currencies[currency[0]]" />
<t t-set="currency" t-value="currency[1]" />
<p>
<span
t-esc="'' if account_type == 'receivable' else 'Supplier '"
/>Statement up to <span t-esc="d['end']" /> in <span
t-esc="display_currency.name"
/>
</p> </p>
<table class="table table-sm table-statement">
<thead>
<tr>
<th>Reference number</th>
<th>Date</th>
<th>Due Date</th>
<th>Description</th>
<th class="amount">Original</th>
<th class="amount">Open Amount</th>
<th class="amount">Balance</th>
</tr>
</thead>
<tbody>
<tr t-foreach="currency['lines']" t-as="line" t-att-class="'statement-blocked' if line['blocked'] else ''">
<td>
<span t-esc="line['move_id']"/>
</td>
<td>
<span t-esc="line['date']"/>
</td>
<td>
<span t-esc="line['date_maturity']"/>
</td>
<td>
<t t-if="line['name'] != '/'">
<t t-if="not line['ref']">
<span t-esc="line['name']"/>
<table class="table table-sm table-statement">
<thead>
<tr>
<th>Reference number</th>
<th>Date</th>
<th>Due Date</th>
<th>Description</th>
<th class="amount">Original</th>
<th class="amount">Open Amount</th>
<th class="amount">Balance</th>
</tr>
</thead>
<tbody>
<tr
t-foreach="currency['lines']"
t-as="line"
t-att-class="'statement-blocked' if line['blocked'] else ''"
>
<td>
<span t-esc="line['move_id']" />
</td>
<td>
<span t-esc="line['date']" />
</td>
<td>
<span t-esc="line['date_maturity']" />
</td>
<td>
<t t-if="line['name'] != '/'">
<t t-if="not line['ref']">
<span t-esc="line['name']" />
</t>
<t t-if="line['ref'] and line['name']">
<t
t-if="line['name'] not in line['ref']"
>
<span t-esc="line['name']" />
</t> </t>
<t t-if="line['ref'] and line['name']">
<t t-if="line['name'] not in line['ref']">
<span t-esc="line['name']"/>
</t>
<t t-if="line['ref'] not in line['name']">
<span t-esc="line['ref']"/>
</t>
<t t-if="line['ref'] == line['name']">
<span t-esc="line['name']"/>
</t>
<t
t-if="line['ref'] not in line['name']"
>
<span t-esc="line['ref']" />
</t>
<t t-if="line['ref'] == line['name']">
<span t-esc="line['name']" />
</t> </t>
</t> </t>
<t t-if="line['name'] == '/'">
<span t-esc="line['ref']"/>
</t>
</td>
<td class="amount">
<span t-esc="line['amount']" t-options="{'widget': 'monetary', 'display_currency': display_currency}"/>
</td>
<td class="amount">
<span t-esc="line['open_amount']" t-options="{'widget': 'monetary', 'display_currency': display_currency}"/>
</td>
<td class="amount">
<span t-esc="line['balance']" t-options="{'widget': 'monetary', 'display_currency': display_currency}"/>
</td>
</tr>
<tr>
<td />
<td>
<span t-esc="d['end']"/>
</td>
<td>
</t>
<t t-if="line['name'] == '/'">
<span t-esc="line['ref']" />
</t>
</td>
<td class="amount">
<span
t-esc="line['amount']"
t-options="{'widget': 'monetary', 'display_currency': display_currency}"
/>
</td>
<td class="amount">
<span
t-esc="line['open_amount']"
t-options="{'widget': 'monetary', 'display_currency': display_currency}"
/>
</td>
<td class="amount">
<span
t-esc="line['balance']"
t-options="{'widget': 'monetary', 'display_currency': display_currency}"
/>
</td>
</tr>
<tr>
<td />
<td>
<span t-esc="d['end']" />
</td>
<td>
Ending Balance Ending Balance
</td> </td>
<td />
<td />
<td />
<td class="amount">
<span t-esc="currency['amount_due']" t-options="{'widget': 'monetary', 'display_currency': display_currency}"/>
</td>
</tr>
</tbody>
</table>
<t t-call="partner_statement.aging_buckets" t-if="currency['buckets']" />
</t>
<td />
<td />
<td />
<td class="amount">
<span
t-esc="currency['amount_due']"
t-options="{'widget': 'monetary', 'display_currency': display_currency}"
/>
</td>
</tr>
</tbody>
</table>
<t
t-call="partner_statement.aging_buckets"
t-if="currency['buckets']"
/>
</t> </t>
<p t-if="d.get('no_entries')">
<strong>The partner doesn't have due entries.</strong>
</p>
</div>
</t>
<p t-if="d.get('no_entries')">
<strong>The partner doesn't have due entries.</strong>
</p>
</div>
</t> </t>
</template> </template>
<template id="outstanding_statement"> <template id="outstanding_statement">
<t t-call="web.html_container"> <t t-call="web.html_container">
<t t-foreach="docs" t-as="o"> <t t-foreach="docs" t-as="o">
<t t-set="d" t-value="data.get(o.id)" /> <t t-set="d" t-value="data.get(o.id)" />
<t t-call="partner_statement.outstanding_statement_document" t-lang="o.lang"/>
<t
t-call="partner_statement.outstanding_statement_document"
t-lang="o.lang"
/>
</t> </t>
</t> </t>
</template> </template>
<report id="action_print_outstanding_statement"
<report
id="action_print_outstanding_statement"
model="res.partner" model="res.partner"
report_type="qweb-pdf" report_type="qweb-pdf"
menu="False" menu="False"
string="Outstanding Statement" string="Outstanding Statement"
name="partner_statement.outstanding_statement" name="partner_statement.outstanding_statement"
file="partner_statement.outstanding_statement" file="partner_statement.outstanding_statement"
/>
/>
</odoo> </odoo>

64
partner_statement/views/res_config_settings.xml

@ -1,11 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<record id="res_config_settings_view_form" model="ir.ui.view"> <record id="res_config_settings_view_form" model="ir.ui.view">
<field name="name">res.config.settings.view.form (in partner_statement)</field> <field name="name">res.config.settings.view.form (in partner_statement)</field>
<field name="model">res.config.settings</field> <field name="model">res.config.settings</field>
<field name="priority" eval="40"/>
<field name="inherit_id" ref="account.res_config_settings_view_form"/>
<field name="priority" eval="40" />
<field name="inherit_id" ref="account.res_config_settings_view_form" />
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//div[@id='account_followup']" position="after"> <xpath expr="//div[@id='account_followup']" position="after">
<div class="col-12 col-lg-6 o_setting_box"> <div class="col-12 col-lg-6 o_setting_box">
@ -13,26 +12,50 @@
<field name="group_activity_statement" /> <field name="group_activity_statement" />
</div> </div>
<div class="o_setting_right_pane"> <div class="o_setting_right_pane">
<label for="group_activity_statement"/>
<label for="group_activity_statement" />
<div class="text-muted"> <div class="text-muted">
Activity Statements show all transactions between two dates. Activity Statements show all transactions between two dates.
</div> </div>
<div class="content-group" attrs="{'invisible': [('group_activity_statement', '=', False), ('group_outstanding_statement', '=', False)]}">
<div
class="content-group"
attrs="{'invisible': [('group_activity_statement', '=', False), ('group_outstanding_statement', '=', False)]}"
>
<div class="row mt16"> <div class="row mt16">
<label for="default_aging_type" class="col-lg-3 o_light_label"/>
<label
for="default_aging_type"
class="col-lg-3 o_light_label"
/>
<field name="default_aging_type" /> <field name="default_aging_type" />
</div> </div>
<div >
<field name="default_show_aging_buckets" class="oe_inline" />
<label for="default_show_aging_buckets" class="o_light_label"/>
<div>
<field
name="default_show_aging_buckets"
class="oe_inline"
/>
<label
for="default_show_aging_buckets"
class="o_light_label"
/>
</div> </div>
<div >
<field name="default_filter_partners_non_due" class="oe_inline" />
<label for="default_filter_partners_non_due" class="o_light_label"/>
<div>
<field
name="default_filter_partners_non_due"
class="oe_inline"
/>
<label
for="default_filter_partners_non_due"
class="o_light_label"
/>
</div> </div>
<div >
<field name="default_filter_negative_balances" class="oe_inline" />
<label for="default_filter_negative_balances" class="o_light_label"/>
<div>
<field
name="default_filter_negative_balances"
class="oe_inline"
/>
<label
for="default_filter_negative_balances"
class="o_light_label"
/>
</div> </div>
</div> </div>
</div> </div>
@ -40,14 +63,16 @@
<div class="col-12 col-lg-6 o_setting_box"> <div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_left_pane"> <div class="o_setting_left_pane">
<field name="group_outstanding_statement" /> <field name="group_outstanding_statement" />
</div> </div>
<div class="o_setting_right_pane"> <div class="o_setting_right_pane">
<label for="group_outstanding_statement"/>
<label for="group_outstanding_statement" />
<div class="text-muted"> <div class="text-muted">
Outstanding Statements show all transactions up to a date. Outstanding Statements show all transactions up to a date.
</div> </div>
<div class="text-muted" attrs="{'invisible': [('group_outstanding_statement', '=', False)]}">
<div
class="text-muted"
attrs="{'invisible': [('group_outstanding_statement', '=', False)]}"
>
Please set defaults under Activity Statements. Please set defaults under Activity Statements.
</div> </div>
</div> </div>
@ -55,5 +80,4 @@
</xpath> </xpath>
</field> </field>
</record> </record>
</odoo> </odoo>

21
partner_statement/wizard/activity_statement_wizard.py

@ -3,29 +3,29 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from odoo import api, fields, models from odoo import api, fields, models
class ActivityStatementWizard(models.TransientModel): class ActivityStatementWizard(models.TransientModel):
"""Activity Statement wizard.""" """Activity Statement wizard."""
_inherit = 'statement.common.wizard'
_name = 'activity.statement.wizard'
_description = 'Activity Statement Wizard'
_inherit = "statement.common.wizard"
_name = "activity.statement.wizard"
_description = "Activity Statement Wizard"
@api.model @api.model
def _get_date_start(self): def _get_date_start(self):
return ( return (
fields.Date.context_today(self).replace(day=1) -
relativedelta(days=1)
fields.Date.context_today(self).replace(day=1) - relativedelta(days=1)
).replace(day=1) ).replace(day=1)
date_start = fields.Date(required=True, default=_get_date_start) date_start = fields.Date(required=True, default=_get_date_start)
@api.onchange('aging_type')
@api.onchange("aging_type")
def onchange_aging_type(self): def onchange_aging_type(self):
super().onchange_aging_type() super().onchange_aging_type()
if self.aging_type == 'months':
if self.aging_type == "months":
self.date_start = self.date_end.replace(day=1) self.date_start = self.date_end.replace(day=1)
else: else:
self.date_start = self.date_end - relativedelta(days=30) self.date_start = self.date_end - relativedelta(days=30)
@ -34,11 +34,10 @@ class ActivityStatementWizard(models.TransientModel):
"""Export to PDF.""" """Export to PDF."""
data = self._prepare_statement() data = self._prepare_statement()
return self.env.ref( return self.env.ref(
'partner_statement'
'.action_print_activity_statement').report_action(
self, data=data)
"partner_statement" ".action_print_activity_statement"
).report_action(self, data=data)
def _prepare_statement(self): def _prepare_statement(self):
res = super()._prepare_statement() res = super()._prepare_statement()
res.update({'date_start': self.date_start})
res.update({"date_start": self.date_start})
return res return res

11
partner_statement/wizard/outstanding_statement_wizard.py

@ -8,14 +8,13 @@ from odoo import models
class OutstandingStatementWizard(models.TransientModel): class OutstandingStatementWizard(models.TransientModel):
"""Outstanding Statement wizard.""" """Outstanding Statement wizard."""
_name = 'outstanding.statement.wizard'
_inherit = 'statement.common.wizard'
_description = 'Outstanding Statement Wizard'
_name = "outstanding.statement.wizard"
_inherit = "statement.common.wizard"
_description = "Outstanding Statement Wizard"
def _export(self): def _export(self):
"""Export to PDF.""" """Export to PDF."""
data = self._prepare_statement() data = self._prepare_statement()
return self.env.ref( return self.env.ref(
'partner_statement'
'.action_print_outstanding_statement').report_action(
self, data=data)
"partner_statement" ".action_print_outstanding_statement"
).report_action(self, data=data)

32
partner_statement/wizard/res_config_settings.py

@ -2,12 +2,12 @@ from odoo import fields, models
class ResConfigSettings(models.TransientModel): class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
_inherit = "res.config.settings"
group_activity_statement = fields.Boolean( group_activity_statement = fields.Boolean(
"Enable OCA Activity Statements", "Enable OCA Activity Statements",
group='account.group_account_invoice',
implied_group='partner_statement.group_activity_statement',
group="account.group_account_invoice",
implied_group="partner_statement.group_activity_statement",
) )
default_aging_type = fields.Selection( default_aging_type = fields.Selection(
@ -19,40 +19,40 @@ class ResConfigSettings(models.TransientModel):
) )
default_show_aging_buckets = fields.Boolean( default_show_aging_buckets = fields.Boolean(
string="Show Aging Buckets",
default_model="statement.common.wizard",
string="Show Aging Buckets", default_model="statement.common.wizard"
) )
default_filter_partners_non_due = fields.Boolean( default_filter_partners_non_due = fields.Boolean(
string='Exclude partners with no due entries',
string="Exclude partners with no due entries",
default_model="statement.common.wizard", default_model="statement.common.wizard",
) )
default_filter_negative_balances = fields.Boolean( default_filter_negative_balances = fields.Boolean(
"Exclude Negative Balances",
default_model="statement.common.wizard",
"Exclude Negative Balances", default_model="statement.common.wizard"
) )
group_outstanding_statement = fields.Boolean( group_outstanding_statement = fields.Boolean(
"Enable OCA Outstanding Statements", "Enable OCA Outstanding Statements",
group='account.group_account_invoice',
implied_group='partner_statement.group_outstanding_statement',
group="account.group_account_invoice",
implied_group="partner_statement.group_outstanding_statement",
) )
def set_values(self): def set_values(self):
self = self.with_context(active_test=False) self = self.with_context(active_test=False)
# default values fields # default values fields
IrDefault = self.env['ir.default'].sudo()
IrDefault = self.env["ir.default"].sudo()
for name, field in self._fields.items(): for name, field in self._fields.items():
if (name.startswith("default_") and
field.default_model == 'statement.common.wizard'):
if (
name.startswith("default_")
and field.default_model == "statement.common.wizard"
):
if isinstance(self[name], models.BaseModel): if isinstance(self[name], models.BaseModel):
if self._fields[name].type == 'many2one':
if self._fields[name].type == "many2one":
value = self[name].id value = self[name].id
else: else:
value = self[name].ids value = self[name].ids
else: else:
value = self[name] value = self[name]
IrDefault.set('activity.statement.wizard', name[8:], value)
IrDefault.set('outstanding.statement.wizard', name[8:], value)
IrDefault.set("activity.statement.wizard", name[8:], value)
IrDefault.set("outstanding.statement.wizard", name[8:], value)
return super().set_values() return super().set_values()

54
partner_statement/wizard/statement_common.py

@ -2,38 +2,37 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from odoo import api, fields, models from odoo import api, fields, models
class StatementCommon(models.AbstractModel): class StatementCommon(models.AbstractModel):
_name = 'statement.common.wizard'
_description = 'Statement Reports Common Wizard'
_name = "statement.common.wizard"
_description = "Statement Reports Common Wizard"
def _get_company(self): def _get_company(self):
return ( return (
self.env['res.company'].browse(
self.env.context.get('force_company')) or
self.env.user.company_id
self.env["res.company"].browse(self.env.context.get("force_company"))
or self.env.user.company_id
) )
name = fields.Char() name = fields.Char()
company_id = fields.Many2one( company_id = fields.Many2one(
comodel_name='res.company',
comodel_name="res.company",
default=_get_company, default=_get_company,
string='Company',
string="Company",
required=True, required=True,
) )
date_end = fields.Date(required=True, default=fields.Date.context_today) date_end = fields.Date(required=True, default=fields.Date.context_today)
show_aging_buckets = fields.Boolean(default=True) show_aging_buckets = fields.Boolean(default=True)
number_partner_ids = fields.Integer( number_partner_ids = fields.Integer(
default=lambda self: len(self._context['active_ids'])
default=lambda self: len(self._context["active_ids"])
) )
filter_partners_non_due = fields.Boolean( filter_partners_non_due = fields.Boolean(
string="Don't show partners with no due entries", default=True)
filter_negative_balances = fields.Boolean(
"Exclude Negative Balances", default=True
string="Don't show partners with no due entries", default=True
) )
filter_negative_balances = fields.Boolean("Exclude Negative Balances", default=True)
aging_type = fields.Selection( aging_type = fields.Selection(
[("days", "Age by Days"), ("months", "Age by Months")], [("days", "Age by Days"), ("months", "Age by Months")],
@ -43,16 +42,17 @@ class StatementCommon(models.AbstractModel):
) )
account_type = fields.Selection( account_type = fields.Selection(
[('receivable', 'Receivable'),
('payable', 'Payable')], string='Account type', default='receivable')
[("receivable", "Receivable"), ("payable", "Payable")],
string="Account type",
default="receivable",
)
@api.onchange('aging_type')
@api.onchange("aging_type")
def onchange_aging_type(self): def onchange_aging_type(self):
if self.aging_type == 'months':
self.date_end = (
fields.Date.context_today(self).replace(day=1) -
relativedelta(days=1)
)
if self.aging_type == "months":
self.date_end = fields.Date.context_today(self).replace(
day=1
) - relativedelta(days=1)
else: else:
self.date_end = fields.Date.context_today(self) self.date_end = fields.Date.context_today(self)
@ -64,14 +64,14 @@ class StatementCommon(models.AbstractModel):
def _prepare_statement(self): def _prepare_statement(self):
self.ensure_one() self.ensure_one()
return { return {
'date_end': self.date_end,
'company_id': self.company_id.id,
'partner_ids': self._context['active_ids'],
'show_aging_buckets': self.show_aging_buckets,
'filter_non_due_partners': self.filter_partners_non_due,
'account_type': self.account_type,
'aging_type': self.aging_type,
'filter_negative_balances': self.filter_negative_balances,
"date_end": self.date_end,
"company_id": self.company_id.id,
"partner_ids": self._context["active_ids"],
"show_aging_buckets": self.show_aging_buckets,
"filter_non_due_partners": self.filter_partners_non_due,
"account_type": self.account_type,
"aging_type": self.aging_type,
"filter_negative_balances": self.filter_negative_balances,
} }
def _export(self): def _export(self):

104
partner_statement/wizard/statement_wizard.xml

@ -1,24 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2018 Eficent Business and IT Consulting Services S.L. <!-- Copyright 2018 Eficent Business and IT Consulting Services S.L.
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). --> License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo> <odoo>
<!-- wizard action on res.partner --> <!-- wizard action on res.partner -->
<act_window id="activity_statement_wizard_action"
<act_window
id="activity_statement_wizard_action"
name="Partner Activity Statement" name="Partner Activity Statement"
src_model="res.partner" src_model="res.partner"
res_model="activity.statement.wizard" res_model="activity.statement.wizard"
view_type="form" view_mode="form"
key2="client_action_multi" target="new"
groups="partner_statement.group_activity_statement"/>
<act_window id="outstanding_statement_wizard_action"
view_type="form"
view_mode="form"
key2="client_action_multi"
target="new"
groups="partner_statement.group_activity_statement"
/>
<act_window
id="outstanding_statement_wizard_action"
name="Partner Outstanding Statement" name="Partner Outstanding Statement"
src_model="res.partner" src_model="res.partner"
res_model="outstanding.statement.wizard" res_model="outstanding.statement.wizard"
view_type="form" view_mode="form"
key2="client_action_multi" target="new"
groups="partner_statement.group_outstanding_statement"/>
view_type="form"
view_mode="form"
key2="client_action_multi"
target="new"
groups="partner_statement.group_outstanding_statement"
/>
<!-- wizard view --> <!-- wizard view -->
<record id="statement_common_view" model="ir.ui.view"> <record id="statement_common_view" model="ir.ui.view">
<field name="name">Statement Common Wizard View</field> <field name="name">Statement Common Wizard View</field>
@ -26,76 +32,98 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<form name="Report Options"> <form name="Report Options">
<div style="text-align:justify" name="info"> <div style="text-align:justify" name="info">
<label string="Aging details can be shown in the report, expressed in aging
buckets, so the partner can review how much is open, due or overdue." for=""/>
</div><hr/>
<label
string="Aging details can be shown in the report, expressed in aging
buckets, so the partner can review how much is open, due or overdue."
for=""
/>
</div>
<hr />
<group> <group>
<group name="main_info"> <group name="main_info">
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
<label for="account_type"/>
<field name="account_type" nolabel="1" widget="radio"/>
<field
name="company_id"
options="{'no_create': True}"
groups="base.group_multi_company"
/>
<label for="account_type" />
<field name="account_type" nolabel="1" widget="radio" />
</group> </group>
<group name="aging_report"> <group name="aging_report">
<field name="show_aging_buckets"/>
<field name="show_aging_buckets" />
<field name="aging_type" /> <field name="aging_type" />
</group> </group>
</group> </group>
<group> <group>
<group name="dates"> <group name="dates">
<field name="date_end"/>
<field name="date_end" />
</group> </group>
<group name="multiple_partners"> <group name="multiple_partners">
<field name="number_partner_ids" readonly="1" invisible="1"/>
<field name="filter_partners_non_due" attrs="{'invisible': [('number_partner_ids', '=', 1)]}" />
<field name="filter_negative_balances" attrs="{'invisible': [('number_partner_ids', '=', 1)]}" />
<field name="number_partner_ids" readonly="1" invisible="1" />
<field
name="filter_partners_non_due"
attrs="{'invisible': [('number_partner_ids', '=', 1)]}"
/>
<field
name="filter_negative_balances"
attrs="{'invisible': [('number_partner_ids', '=', 1)]}"
/>
</group> </group>
</group> </group>
<footer> <footer>
<button name="button_export_pdf" string="Export PDF" type="object" default_focus="1" class="oe_highlight"/>
<button
name="button_export_pdf"
string="Export PDF"
type="object"
default_focus="1"
class="oe_highlight"
/>
or or
<button string="Cancel" class="oe_link" special="cancel" /> <button string="Cancel" class="oe_link" special="cancel" />
</footer> </footer>
</form> </form>
</field> </field>
</record> </record>
<record id="outstanding_statement_wizard_view" model="ir.ui.view"> <record id="outstanding_statement_wizard_view" model="ir.ui.view">
<field name="name">Outstanding Statement Wizard</field> <field name="name">Outstanding Statement Wizard</field>
<field name="model">outstanding.statement.wizard</field> <field name="model">outstanding.statement.wizard</field>
<field name="inherit_id" ref="partner_statement.statement_common_view" /> <field name="inherit_id" ref="partner_statement.statement_common_view" />
<field name="mode">primary</field> <field name="mode">primary</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//div[@name='info']/label" position="before" >
<label string="The outstanding statement provides details of all partner's outstanding
<xpath expr="//div[@name='info']/label" position="before">
<label
string="The outstanding statement provides details of all partner's outstanding
receivables and payables up to a particular date. This includes all unpaid invoices, unclaimed receivables and payables up to a particular date. This includes all unpaid invoices, unclaimed
refunds and outstanding payments. The list is displayed in chronological order and is refunds and outstanding payments. The list is displayed in chronological order and is
split by currencies." for=""/>
<br/><br/>
split by currencies."
for=""
/>
<br />
<br />
</xpath> </xpath>
</field> </field>
</record> </record>
<record id="activity_statement_wizard_view" model="ir.ui.view"> <record id="activity_statement_wizard_view" model="ir.ui.view">
<field name="name">Activity Statement Wizard</field> <field name="name">Activity Statement Wizard</field>
<field name="model">activity.statement.wizard</field> <field name="model">activity.statement.wizard</field>
<field name="inherit_id" ref="partner_statement.statement_common_view" /> <field name="inherit_id" ref="partner_statement.statement_common_view" />
<field name="mode">primary</field> <field name="mode">primary</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//div[@name='info']/label" position="before" >
<label string="The activity statement provides details of all activity on
<xpath expr="//div[@name='info']/label" position="before">
<label
string="The activity statement provides details of all activity on
a partner's receivables and payables between two selected dates. This includes all invoices, a partner's receivables and payables between two selected dates. This includes all invoices,
refunds and payments. Any outstanding balance dated prior to the chosen statement refunds and payments. Any outstanding balance dated prior to the chosen statement
period will appear as a forward balance at the top of the statement. The list is period will appear as a forward balance at the top of the statement. The list is
displayed in chronological order and is split by currencies." for=""/>
<br/><br/>
displayed in chronological order and is split by currencies."
for=""
/>
<br />
<br />
</xpath> </xpath>
<xpath expr="//field[@name='date_end']" position="before"> <xpath expr="//field[@name='date_end']" position="before">
<field name="date_start"/>
<field name="date_start" />
</xpath> </xpath>
</field> </field>
</record> </record>
</odoo> </odoo>
Loading…
Cancel
Save