diff --git a/partner_statement/__manifest__.py b/partner_statement/__manifest__.py index f7fd35b3..9678a23a 100644 --- a/partner_statement/__manifest__.py +++ b/partner_statement/__manifest__.py @@ -3,25 +3,23 @@ # 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, } diff --git a/partner_statement/readme/DESCRIPTION.rst b/partner_statement/readme/DESCRIPTION.rst index 169136b1..007ebf3b 100644 --- a/partner_statement/readme/DESCRIPTION.rst +++ b/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, so the customer or vendor can review how much is open, due or overdue. diff --git a/partner_statement/report/activity_statement.py b/partner_statement/report/activity_statement.py index 29512803..b45d6873 100644 --- a/partner_statement/report/activity_statement.py +++ b/partner_statement/report/activity_statement.py @@ -2,18 +2,21 @@ # (http://www.eficent.com) # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). -from odoo import api, models from collections import defaultdict +from odoo import api, models + class ActivityStatement(models.AbstractModel): """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): - return str(self._cr.mogrify(""" + return str( + self._cr.mogrify( + """ SELECT l.partner_id, l.currency_id, l.company_id, CASE WHEN l.currency_id is not null AND l.amount_currency > 0.0 THEN sum(l.amount_currency) @@ -31,34 +34,50 @@ class ActivityStatement(models.AbstractModel): AND m.state IN ('posted') GROUP BY l.partner_id, l.currency_id, l.amount_currency, l.company_id - """, locals()), "utf-8") + """, + locals(), + ), + "utf-8", + ) 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, COALESCE(Q1.currency_id, c.currency_id) AS currency_id FROM Q1 JOIN res_company c ON (c.id = Q1.company_id) 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) partners = tuple(partner_ids) # 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 - 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(): - balance_start[row.pop('partner_id')].append(row) + balance_start[row.pop("partner_id")].append(row) 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, CASE WHEN (aj.type IN ('sale', 'purchase')) THEN l.name @@ -104,10 +123,16 @@ class ActivityStatement(models.AbstractModel): ELSE '' END, 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): - return str(self._cr.mogrify(""" + return str( + self._cr.mogrify( + """ SELECT Q1.partner_id, Q1.move_id, Q1.date, Q1.date_maturity, Q1.name, Q1.ref, Q1.debit, Q1.credit, Q1.debit-Q1.credit as amount, Q1.blocked, @@ -115,36 +140,46 @@ class ActivityStatement(models.AbstractModel): FROM Q1 JOIN res_company c ON (c.id = Q1.company_id) 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)) partners = tuple(partner_ids) # pylint: disable=E8103 - self.env.cr.execute(""" + self.env.cr.execute( + """ WITH Q1 AS (%s), Q2 AS (%s) SELECT partner_id, move_id, date, date_maturity, name, ref, debit, credit, amount, blocked, currency_id 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(): - res[row.pop('partner_id')].append(row) + res[row.pop("partner_id")].append(row) return res @api.multi def _get_report_values(self, docids, data): if not data: data = {} - if 'company_id' not in data: + if "company_id" not in data: wiz = self.env["activity.statement.wizard"].with_context( active_ids=docids, model="res.partner" ) data.update(wiz.create({})._prepare_statement()) - data['amount_field'] = 'amount' + data["amount_field"] = "amount" return super()._get_report_values(docids, data) diff --git a/partner_statement/report/outstanding_statement.py b/partner_statement/report/outstanding_statement.py index 7388b17e..63b42cbe 100644 --- a/partner_statement/report/outstanding_statement.py +++ b/partner_statement/report/outstanding_statement.py @@ -8,12 +8,14 @@ from odoo import api, models class OutstandingStatement(models.AbstractModel): """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): 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, l.ref, l.blocked, l.currency_id, l.company_id, 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, l.ref, l.blocked, l.currency_id, l.balance, l.amount_currency, l.company_id - """, locals()), "utf-8" + """, + locals(), + ), + "utf-8", ) def _display_lines_sql_q2(self): return str( - self._cr.mogrify(""" + self._cr.mogrify( + """ SELECT Q1.partner_id, Q1.currency_id, Q1.move_id, Q1.date, Q1.date_maturity, Q1.debit, Q1.credit, Q1.name, Q1.ref, Q1.blocked, Q1.company_id, @@ -76,13 +82,16 @@ class OutstandingStatement(models.AbstractModel): ELSE Q1.open_amount END as open_amount FROM Q1 - """, locals() + """, + locals(), ), - "utf-8" + "utf-8", ) 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, Q2.name, Q2.ref, Q2.debit, Q2.credit, Q2.debit-Q2.credit AS amount, blocked, @@ -91,37 +100,45 @@ class OutstandingStatement(models.AbstractModel): FROM Q2 JOIN res_company c ON (c.id = Q2.company_id) 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)) partners = tuple(partner_ids) # pylint: disable=E8103 - self.env.cr.execute(""" + self.env.cr.execute( + """ WITH Q1 as (%s), Q2 AS (%s), Q3 AS (%s) SELECT partner_id, currency_id, move_id, date, date_maturity, debit, credit, amount, open_amount, name, ref, blocked 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(): - res[row.pop('partner_id')].append(row) + res[row.pop("partner_id")].append(row) return res @api.multi def _get_report_values(self, docids, data): if not data: data = {} - if 'company_id' not in data: + if "company_id" not in data: wiz = self.env["outstanding.statement.wizard"].with_context( active_ids=docids, model="res.partner" ) data.update(wiz.create({})._prepare_statement()) - data['amount_field'] = 'open_amount' + data["amount_field"] = "open_amount" return super()._get_report_values(docids, data) diff --git a/partner_statement/report/report_statement_common.py b/partner_statement/report/report_statement_common.py index 60b0e004..09ebb586 100644 --- a/partner_statement/report/report_statement_common.py +++ b/partner_statement/report/report_statement_common.py @@ -3,39 +3,42 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). from datetime import datetime, timedelta + +from odoo import _, api, fields, models from odoo.tools.misc import DEFAULT_SERVER_DATE_FORMAT -from odoo import api, fields, models, _ class ReportStatementCommon(models.AbstractModel): """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): - 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) 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): 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 - 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 {} 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, CASE WHEN l.balance > 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, l.amount_currency, l.balance, l.move_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, open_due_currency, move_id, company_id, CASE @@ -146,17 +154,27 @@ class ReportStatementCommon(models.AbstractModel): FROM Q1 GROUP BY partner_id, currency_id, date_maturity, open_due, open_due_currency, move_id, company_id - """, locals()), "utf-8") + """, + locals(), + ), + "utf-8", + ) 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, b_over_120, COALESCE(Q2.currency_id, c.currency_id) AS currency_id FROM Q2 JOIN res_company c ON (c.id = Q2.company_id) WHERE c.id = %(company_id)s - """, locals()), "utf-8") + """, + locals(), + ), + "utf-8", + ) def _show_buckets_sql_q4(self): return """ @@ -172,41 +190,36 @@ class ReportStatementCommon(models.AbstractModel): def _get_bucket_dates(self, date_end, aging_type): 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) def _get_bucket_dates_days(self, date_end): 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): res = {} 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 d = d.replace(day=1) - timedelta(days=1) 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)) partners = tuple(partner_ids) full_dates = self._get_bucket_dates(date_end, aging_type) # pylint: disable=E8103 # All input queries are properly escaped - false positive - self.env.cr.execute(""" + self.env.cr.execute( + """ WITH Q1 AS (%s), Q2 AS (%s), Q3 AS (%s), @@ -217,63 +230,63 @@ class ReportStatementCommon(models.AbstractModel): AS balance FROM Q4 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(): - buckets[row.pop('partner_id')].append(row) + buckets[row.pop("partner_id")].append(row) return buckets def _get_bucket_labels(self, date_end, aging_type): 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) def _get_bucket_labels_days(self, date_end): 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): 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: # 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 ( { - 'lines': [], - 'buckets': [], - 'balance_forward': balance_forward, - 'amount_due': balance_forward, + "lines": [], + "buckets": [], + "balance_forward": balance_forward, + "amount_due": balance_forward, }, - currencies + currencies, ) @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): date_start = datetime.strptime( date_start, DEFAULT_SERVER_DATE_FORMAT ).date() - date_end = data['date_end'] + date_end = data["date_end"] 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() - 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 # we cache them - default needed if partner lang not set - self._cr.execute(""" + self._cr.execute( + """ SELECT p.id, l.date_format FROM res_partner p LEFT JOIN res_lang l ON p.lang=l.code 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()} - 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 = {} # get base data 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( - 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( - 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) else: bucket_labels = {} @@ -346,69 +362,65 @@ class ReportStatementCommon(models.AbstractModel): partners_to_remove = set() for partner_id in partner_ids: 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, []): - 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]: - 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]: - 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: 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"]: partners_to_remove.add(partner_id) continue else: - res[partner_id]['no_entries'] = True + res[partner_id]["no_entries"] = True 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) for partner in partners_to_remove: @@ -416,13 +428,13 @@ class ReportStatementCommon(models.AbstractModel): partner_ids.remove(partner) 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, } diff --git a/partner_statement/security/statement_security.xml b/partner_statement/security/statement_security.xml index b04edfcf..ed06aeab 100644 --- a/partner_statement/security/statement_security.xml +++ b/partner_statement/security/statement_security.xml @@ -1,14 +1,11 @@ - + - Use activity statements - + - Use outstanding statements - + - diff --git a/partner_statement/static/src/scss/layout_statement.scss b/partner_statement/static/src/scss/layout_statement.scss index 80cd8edc..b5851829 100644 --- a/partner_statement/static/src/scss/layout_statement.scss +++ b/partner_statement/static/src/scss/layout_statement.scss @@ -1,16 +1,16 @@ .table-statement { .amount { text-align: right !important; - width: 14% //spread 7 columns evenly + width: 14%; //spread 7 columns evenly } thead { border-bottom: solid; // required for clean layout tr th:first-child { width: auto !important; // required for clean layout - } + } tr th:last-child { width: 16% !important; // required for boxed layout - } + } } } diff --git a/partner_statement/tests/test_activity_statement.py b/partner_statement/tests/test_activity_statement.py index 3088a701..4247cbc1 100644 --- a/partner_statement/tests/test_activity_statement.py +++ b/partner_statement/tests/test_activity_statement.py @@ -3,108 +3,112 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). from datetime import date -from odoo.tests.common import TransactionCase + from odoo import fields +from odoo.tests.common import TransactionCase class TestActivityStatement(TransactionCase): """ Tests for Activity Statement. """ + def setUp(self): 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) def _create_user(self, login, groups, company): 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 def test_customer_activity_statement(self): wiz_id = self.wiz.with_context( - active_ids=[self.partner1.id, self.partner2.id], + active_ids=[self.partner1.id, self.partner2.id] ).create({}) - wiz_id.aging_type = 'months' + wiz_id.aging_type = "months" wiz_id.show_aging_buckets = False statement = wiz_id.button_export_pdf() 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, - '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() - docids = data['partner_ids'] + docids = data["partner_ids"] 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): docids = [self.partner1.id, self.partner2.id] 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): - date_fmt = '%d/%m/%Y' + date_fmt = "%d/%m/%Y" 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( test_date_string, date_fmt ) - self.assertEqual(res, '30/09/2018') + self.assertEqual(res, "30/09/2018") def test_onchange_aging_type(self): """Test that partner data is filled accodingly""" wiz_id = self.wiz.with_context( - active_ids=[self.partner1.id, self.partner2.id], + active_ids=[self.partner1.id, self.partner2.id] ).new() - wiz_id.aging_type = 'months' + wiz_id.aging_type = "months" wiz_id.onchange_aging_type() 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 < self.today) - wiz_id.aging_type = 'days' + wiz_id.aging_type = "days" wiz_id.onchange_aging_type() self.assertEqual((wiz_id.date_end - wiz_id.date_start).days, 30) self.assertTrue(wiz_id.date_end == self.today) diff --git a/partner_statement/tests/test_outstanding_statement.py b/partner_statement/tests/test_outstanding_statement.py index 60e88e2f..984595da 100644 --- a/partner_statement/tests/test_outstanding_statement.py +++ b/partner_statement/tests/test_outstanding_statement.py @@ -9,68 +9,74 @@ class TestOutstandingStatement(TransactionCase): """ Tests for Outstanding Statement. """ + def setUp(self): 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): 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 def test_customer_outstanding_statement(self): wiz_id = self.wiz.with_context( - active_ids=[self.partner1.id, self.partner2.id], + active_ids=[self.partner1.id, self.partner2.id] ).create({}) - wiz_id.aging_type = 'months' + wiz_id.aging_type = "months" statement = wiz_id.button_export_pdf() 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, - '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() - docids = data['partner_ids'] + docids = data["partner_ids"] 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): docids = [self.partner1.id] 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." + ) diff --git a/partner_statement/tests/test_res_config_settings.py b/partner_statement/tests/test_res_config_settings.py index c9a1f317..0724a378 100644 --- a/partner_statement/tests/test_res_config_settings.py +++ b/partner_statement/tests/test_res_config_settings.py @@ -4,10 +4,9 @@ from odoo.tests.common import TransactionCase class TestResConfigSettings(TransactionCase): - def setUp(self): super().setUp() - self.config = self.env['res.config.settings'] + self.config = self.env["res.config.settings"] self.cr.execute( "SELECT uid FROM res_groups_users_rel " "WHERE gid IN (SELECT res_id FROM ir_model_data " @@ -15,22 +14,22 @@ class TestResConfigSettings(TransactionCase): "ORDER BY uid DESC LIMIT 1" ) 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): - 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") diff --git a/partner_statement/views/activity_statement.xml b/partner_statement/views/activity_statement.xml index f31004e1..0e293bd3 100644 --- a/partner_statement/views/activity_statement.xml +++ b/partner_statement/views/activity_statement.xml @@ -1,140 +1,172 @@ - + - - - - - + /> diff --git a/partner_statement/views/aging_buckets.xml b/partner_statement/views/aging_buckets.xml index ce8c2687..732e5c0f 100644 --- a/partner_statement/views/aging_buckets.xml +++ b/partner_statement/views/aging_buckets.xml @@ -1,11 +1,12 @@ - + - - diff --git a/partner_statement/views/assets.xml b/partner_statement/views/assets.xml index 118d979e..17f69456 100644 --- a/partner_statement/views/assets.xml +++ b/partner_statement/views/assets.xml @@ -1,12 +1,18 @@ - + - -