diff --git a/addons/account/report/__init__.py b/addons/account/report/__init__.py index 085f3cea256..7c1966465e6 100644 --- a/addons/account/report/__init__.py +++ b/addons/account/report/__init__.py @@ -3,5 +3,4 @@ from . import account_invoice_report from . import account_journal -from . import account_aged_partner_balance from . import account_hash_integrity_templates diff --git a/addons/account/report/account_aged_partner_balance.py b/addons/account/report/account_aged_partner_balance.py deleted file mode 100644 index 0b678992f25..00000000000 --- a/addons/account/report/account_aged_partner_balance.py +++ /dev/null @@ -1,262 +0,0 @@ -# -*- coding: utf-8 -*- - -import time -from odoo import api, fields, models, _ -from odoo.exceptions import UserError -from odoo.tools import float_is_zero -from datetime import datetime -from dateutil.relativedelta import relativedelta - -class ReportAgedPartnerBalance(models.AbstractModel): - - _name = 'report.account.report_agedpartnerbalance' - _description = 'Aged Partner Balance Report' - - def _get_partner_move_lines(self, account_type, date_from, target_move, period_length): - # This method can receive the context key 'include_nullified_amount' {Boolean} - # Do an invoice and a payment and unreconcile. The amount will be nullified - # By default, the partner wouldn't appear in this report. - # The context key allow it to appear - # In case of a period_length of 30 days as of 2019-02-08, we want the following periods: - # Name Stop Start - # 1 - 30 : 2019-02-07 - 2019-01-09 - # 31 - 60 : 2019-01-08 - 2018-12-10 - # 61 - 90 : 2018-12-09 - 2018-11-10 - # 91 - 120 : 2018-11-09 - 2018-10-11 - # +120 : 2018-10-10 - ctx = self._context - periods = {} - date_from = fields.Date.from_string(date_from) - start = date_from - for i in range(5)[::-1]: - stop = start - relativedelta(days=period_length) - period_name = str((5-(i+1)) * period_length + 1) + '-' + str((5-i) * period_length) - period_stop = (start - relativedelta(days=1)).strftime('%Y-%m-%d') - if i == 0: - period_name = '+' + str(4 * period_length) - periods[str(i)] = { - 'name': period_name, - 'stop': period_stop, - 'start': (i!=0 and stop.strftime('%Y-%m-%d') or False), - } - start = stop - - res = [] - total = [] - partner_clause = '' - cr = self.env.cr - user_company = self.env.company - user_currency = user_company.currency_id - company_ids = self._context.get('company_ids') or [user_company.id] - move_state = ['draft', 'posted'] - if target_move == 'posted': - move_state = ['posted'] - arg_list = (tuple(move_state), tuple(account_type)) - #build the reconciliation clause to see what partner needs to be printed - reconciliation_clause = '(l.reconciled IS FALSE)' - cr.execute('SELECT debit_move_id, credit_move_id FROM account_partial_reconcile where max_date > %s', (date_from,)) - reconciled_after_date = [] - for row in cr.fetchall(): - reconciled_after_date += [row[0], row[1]] - if reconciled_after_date: - reconciliation_clause = '(l.reconciled IS FALSE OR l.id IN %s)' - arg_list += (tuple(reconciled_after_date),) - if ctx.get('partner_ids'): - partner_clause = 'AND (l.partner_id IN %s)' - arg_list += (tuple(ctx['partner_ids'].ids),) - if ctx.get('partner_categories'): - partner_clause += 'AND (l.partner_id IN %s)' - partner_ids = self.env['res.partner'].search([('category_id', 'in', ctx['partner_categories'].ids)]).ids - arg_list += (tuple(partner_ids or [0]),) - arg_list += (date_from, tuple(company_ids)) - - query = ''' - SELECT DISTINCT l.partner_id, res_partner.name AS name, UPPER(res_partner.name) AS UPNAME, CASE WHEN prop.value_text IS NULL THEN 'normal' ELSE prop.value_text END AS trust - FROM account_move_line AS l - LEFT JOIN res_partner ON l.partner_id = res_partner.id - LEFT JOIN ir_property prop ON (prop.res_id = 'res.partner,'||res_partner.id AND prop.name='trust' AND prop.company_id=%s), - account_account, account_move am - WHERE (l.account_id = account_account.id) - AND (l.move_id = am.id) - AND (am.state IN %s) - AND (account_account.internal_type IN %s) - AND ''' + reconciliation_clause + partner_clause + ''' - AND (l.date <= %s) - AND l.company_id IN %s - ORDER BY UPPER(res_partner.name)''' - arg_list = (self.env.company.id,) + arg_list - cr.execute(query, arg_list) - - partners = cr.dictfetchall() - # put a total of 0 - for i in range(7): - total.append(0) - - # Build a string like (1,2,3) for easy use in SQL query - partner_ids = [partner['partner_id'] for partner in partners if partner['partner_id']] - lines = dict((partner['partner_id'] or False, []) for partner in partners) - if not partner_ids: - return [], [], {} - - # Use one query per period and store results in history (a list variable) - # Each history will contain: history[1] = {'': } - history = [] - for i in range(5): - args_list = (tuple(move_state), tuple(account_type), tuple(partner_ids),) - dates_query = '(COALESCE(l.date_maturity,l.date)' - - if periods[str(i)]['start'] and periods[str(i)]['stop']: - dates_query += ' BETWEEN %s AND %s)' - args_list += (periods[str(i)]['start'], periods[str(i)]['stop']) - elif periods[str(i)]['start']: - dates_query += ' >= %s)' - args_list += (periods[str(i)]['start'],) - else: - dates_query += ' <= %s)' - args_list += (periods[str(i)]['stop'],) - args_list += (date_from, tuple(company_ids)) - - query = '''SELECT l.id - FROM account_move_line AS l, account_account, account_move am - WHERE (l.account_id = account_account.id) AND (l.move_id = am.id) - AND (am.state IN %s) - AND (account_account.internal_type IN %s) - AND ((l.partner_id IN %s) OR (l.partner_id IS NULL)) - AND ''' + dates_query + ''' - AND (l.date <= %s) - AND l.company_id IN %s - ORDER BY COALESCE(l.date_maturity, l.date)''' - cr.execute(query, args_list) - partners_amount = {} - aml_ids = cr.fetchall() - aml_ids = aml_ids and [x[0] for x in aml_ids] or [] - for line in self.env['account.move.line'].browse(aml_ids).with_context(prefetch_fields=False): - partner_id = line.partner_id.id or False - if partner_id not in partners_amount: - partners_amount[partner_id] = 0.0 - line_amount = line.company_id.currency_id._convert(line.balance, user_currency, user_company, date_from) - if user_currency.is_zero(line_amount): - continue - for partial_line in line.matched_debit_ids: - if partial_line.max_date <= date_from: - line_amount += partial_line.company_id.currency_id._convert(partial_line.amount, user_currency, user_company, date_from) - for partial_line in line.matched_credit_ids: - if partial_line.max_date <= date_from: - line_amount -= partial_line.company_id.currency_id._convert(partial_line.amount, user_currency, user_company, date_from) - - if not self.env.company.currency_id.is_zero(line_amount): - partners_amount[partner_id] += line_amount - lines.setdefault(partner_id, []) - lines[partner_id].append({ - 'line': line, - 'amount': line_amount, - 'period': i + 1, - }) - history.append(partners_amount) - - # This dictionary will store the not due amount of all partners - undue_amounts = {} - query = '''SELECT l.id - FROM account_move_line AS l, account_account, account_move am - WHERE (l.account_id = account_account.id) AND (l.move_id = am.id) - AND (am.state IN %s) - AND (account_account.internal_type IN %s) - AND (COALESCE(l.date_maturity,l.date) >= %s)\ - AND ((l.partner_id IN %s) OR (l.partner_id IS NULL)) - AND (l.date <= %s) - AND l.company_id IN %s - ORDER BY COALESCE(l.date_maturity, l.date)''' - cr.execute(query, (tuple(move_state), tuple(account_type), date_from, tuple(partner_ids), date_from, tuple(company_ids))) - aml_ids = cr.fetchall() - aml_ids = aml_ids and [x[0] for x in aml_ids] or [] - for line in self.env['account.move.line'].browse(aml_ids): - partner_id = line.partner_id.id or False - if partner_id not in undue_amounts: - undue_amounts[partner_id] = 0.0 - line_amount = line.company_id.currency_id._convert(line.balance, user_currency, user_company, date_from) - if user_currency.is_zero(line_amount): - continue - for partial_line in line.matched_debit_ids: - if partial_line.max_date <= date_from: - line_amount += partial_line.company_id.currency_id._convert(partial_line.amount, user_currency, user_company, date_from) - for partial_line in line.matched_credit_ids: - if partial_line.max_date <= date_from: - line_amount -= partial_line.company_id.currency_id._convert(partial_line.amount, user_currency, user_company, date_from) - if not self.env.company.currency_id.is_zero(line_amount): - undue_amounts[partner_id] += line_amount - lines.setdefault(partner_id, []) - lines[partner_id].append({ - 'line': line, - 'amount': line_amount, - 'period': 6, - }) - - for partner in partners: - if partner['partner_id'] is None: - partner['partner_id'] = False - at_least_one_amount = False - values = {} - undue_amt = 0.0 - if partner['partner_id'] in undue_amounts: # Making sure this partner actually was found by the query - undue_amt = undue_amounts[partner['partner_id']] - - total[6] = total[6] + undue_amt - values['direction'] = undue_amt - if not float_is_zero(values['direction'], precision_rounding=self.env.company.currency_id.rounding): - at_least_one_amount = True - - for i in range(5): - during = False - if partner['partner_id'] in history[i]: - during = [history[i][partner['partner_id']]] - # Adding counter - total[(i)] = total[(i)] + (during and during[0] or 0) - values[str(i)] = during and during[0] or 0.0 - if not float_is_zero(values[str(i)], precision_rounding=self.env.company.currency_id.rounding): - at_least_one_amount = True - values['total'] = sum([values['direction']] + [values[str(i)] for i in range(5)]) - # Add for total - total[(i + 1)] += values['total'] - values['partner_id'] = partner['partner_id'] - if partner['partner_id']: - values['name'] = len(partner['name']) >= 45 and partner['name'][0:40] + '...' or partner['name'] - values['trust'] = partner['trust'] - else: - values['name'] = _('Unknown Partner') - values['trust'] = False - - if at_least_one_amount or (self._context.get('include_nullified_amount') and lines[partner['partner_id']]): - res.append(values) - return res, total, lines - - @api.model - def _get_report_values(self, docids, data=None): - if not data.get('form') or not self.env.context.get('active_model') or not self.env.context.get('active_id'): - raise UserError(_("Form content is missing, this report cannot be printed.")) - - total = [] - model = self.env.context.get('active_model') - docs = self.env[model].browse(self.env.context.get('active_id')) - - target_move = data['form'].get('target_move', 'all') - date_from = fields.Date.from_string(data['form'].get('date_from')) or fields.Date.today() - - if data['form']['result_selection'] == 'customer': - account_type = ['receivable'] - elif data['form']['result_selection'] == 'supplier': - account_type = ['payable'] - else: - account_type = ['payable', 'receivable'] - - movelines, total, dummy = self._get_partner_move_lines(account_type, date_from, target_move, data['form']['period_length']) - return { - 'doc_ids': self.ids, - 'doc_model': model, - 'data': data['form'], - 'docs': docs, - 'time': time, - 'get_partner_lines': movelines, - 'get_direction': total, - 'company_id': self.env['res.company'].browse( - data['form']['company_id'][0]), - } diff --git a/addons/account/tests/test_reconciliation.py b/addons/account/tests/test_reconciliation.py index eeb3aa6f13f..b092019b7b5 100644 --- a/addons/account/tests/test_reconciliation.py +++ b/addons/account/tests/test_reconciliation.py @@ -915,48 +915,6 @@ class TestReconciliationExec(TestReconciliation): self.assertEqual(reversed_customer_line.full_reconcile_id.id, customer_line.full_reconcile_id.id) - def test_aged_report(self): - AgedReport = self.env['report.account.report_agedpartnerbalance'].with_context(include_nullified_amount=True) - account_type = ['receivable'] - report_date_to = time.strftime('%Y') + '-07-17' - partner = self.env['res.partner'].create({'name': 'AgedPartner'}) - currency = self.env.company.currency_id - - invoice = self.create_invoice_partner(currency_id=currency.id, partner_id=partner.id) - journal = self.env['account.journal'].create({'name': 'Bank', 'type': 'bank', 'code': 'THE'}) - - statement = self.make_payment(invoice, journal, 50) - - # The report searches on the create_date to dispatch reconciled lines to report periods - # Also, in this case, there can be only 1 partial_reconcile - statement_partial_id = statement.move_line_ids.mapped(lambda l: l.matched_credit_ids + l.matched_debit_ids) - self.env.cr.execute('UPDATE account_partial_reconcile SET create_date = %(date)s WHERE id = %(partial_id)s', - {'date': report_date_to + ' 00:00:00', - 'partial_id': statement_partial_id.id}) - - # Case 1: The invoice and payment are reconciled: Nothing should appear - report_lines, total, amls = AgedReport._get_partner_move_lines(account_type, report_date_to, 'posted', 30) - - partner_lines = [line for line in report_lines if line['partner_id'] == partner.id] - self.assertEqual(partner_lines, [], 'The aged receivable shouldn\'t have lines at this point') - self.assertFalse(amls.get(partner.id, False), 'The aged receivable should not have amls either') - - # Case 2: The invoice and payment are not reconciled: we should have one line on the report - # and 2 amls - invoice.line_ids.with_context(invoice_id=invoice.id).remove_move_reconcile() - report_lines, total, amls = AgedReport._get_partner_move_lines(account_type, report_date_to, 'posted', 30) - - partner_lines = [line for line in report_lines if line['partner_id'] == partner.id] - self.assertEqual(partner_lines, [{'trust': 'normal', '1': 0.0, '0': 0.0, 'direction': 0.0, 'partner_id': partner.id, '3': 0.0, 'total': 0.0, 'name': 'AgedPartner', '4': 0.0, '2': 0.0}], - 'We should have a line in the report for the partner') - self.assertEqual(len(amls[partner.id]), 2, 'We should have 2 account move lines for the partner') - - positive_line = [line for line in amls[partner.id] if line['line'].balance > 0] - negative_line = [line for line in amls[partner.id] if line['line'].balance < 0] - - self.assertEqual(positive_line[0]['amount'], 50.0, 'The amount of the amls should be 50') - self.assertEqual(negative_line[0]['amount'], -50.0, 'The amount of the amls should be -50') - def test_revert_payment_and_reconcile_exchange(self): # A reversal of a reconciled payment which created a currency exchange entry, should create reversal moves @@ -1025,84 +983,6 @@ class TestReconciliationExec(TestReconciliation): _move_revert_test_pair(payment_move, reverted_payment_move) _move_revert_test_pair(exchange_move, reverted_exchange_move) - def test_aged_report_future_payment(self): - AgedReport = self.env['report.account.report_agedpartnerbalance'].with_context(include_nullified_amount=True) - account_type = ['receivable'] - partner = self.env['res.partner'].create({'name': 'AgedPartner'}) - currency = self.env.company.currency_id - - invoice = self.create_invoice_partner(currency_id=currency.id, partner_id=partner.id) - journal = self.env['account.journal'].create({'name': 'Bank', 'type': 'bank', 'code': 'THE'}) - - statement = self.make_payment(invoice, journal, 50) - - # Force the payment recording to take place on the invoice date - # Although the payment due_date is in the future relative to the invoice - # Also, in this case, there can be only 1 partial_reconcile - statement_partial_id = statement.move_line_ids.mapped(lambda l: l.matched_credit_ids + l.matched_debit_ids) - self.env.cr.execute('UPDATE account_partial_reconcile SET create_date = %(date)s WHERE id = %(partial_id)s', - {'date': invoice.invoice_date, - 'partial_id': statement_partial_id.id}) - statement.flush() - - # Case 1: report date is invoice date - # There should be an entry for the partner - report_date_to = invoice.invoice_date - report_lines, total, amls = AgedReport._get_partner_move_lines(account_type, report_date_to, 'posted', 30) - - partner_lines = [line for line in report_lines if line['partner_id'] == partner.id] - self.assertEqual(partner_lines, [{ - 'name': 'AgedPartner', - 'trust': 'normal', - 'partner_id': partner.id, - '0': 0.0, - '1': 0.0, - '2': 0.0, - '3': 0.0, - '4': 0.0, - 'total': 50.0, - 'direction': 50.0, - }], 'We should have a line in the report for the partner') - self.assertEqual(len(amls[partner.id]), 1, 'We should have 1 account move lines for the partner') - - positive_line = [line for line in amls[partner.id] if line['line'].balance > 0] - - self.assertEqual(positive_line[0]['amount'], 50.0, 'The amount of the amls should be 50') - - # Case 2: report date between invoice date and payment date - # There should be an entry for the partner - # And the amount has shifted to '1-30 due' - report_date_to = time.strftime('%Y') + '-07-08' - report_lines, total, amls = AgedReport._get_partner_move_lines(account_type, report_date_to, 'posted', 30) - - partner_lines = [line for line in report_lines if line['partner_id'] == partner.id] - self.assertEqual(partner_lines, [{ - 'name': 'AgedPartner', - 'trust': 'normal', - 'partner_id': partner.id, - '0': 0.0, - '1': 0.0, - '2': 0.0, - '3': 0.0, - '4': 50.0, - 'total': 50.0, - 'direction': 0.0, - }], 'We should have a line in the report for the partner') - self.assertEqual(len(amls[partner.id]), 1, 'We should have 1 account move lines for the partner') - - positive_line = [line for line in amls[partner.id] if line['line'].balance > 0] - - self.assertEqual(positive_line[0]['amount'], 50.0, 'The amount of the amls should be 50') - - # Case 2: report date on payment date - # There should not be an entry for the partner - report_date_to = time.strftime('%Y') + '-07-15' - report_lines, total, amls = AgedReport._get_partner_move_lines(account_type, report_date_to, 'posted', 30) - - partner_lines = [line for line in report_lines if line['partner_id'] == partner.id] - self.assertEqual(partner_lines, [], 'The aged receivable shouldn\'t have lines at this point') - self.assertFalse(amls.get(partner.id, False), 'The aged receivable should not have amls either') - def test_partial_reconcile_currencies_02(self): #### # Day 1: Invoice Cust/001 to customer (expressed in USD) diff --git a/addons/account/views/account_report.xml b/addons/account/views/account_report.xml index 11d6e792f47..034e0a5303c 100644 --- a/addons/account/views/account_report.xml +++ b/addons/account/views/account_report.xml @@ -37,16 +37,6 @@ print_report_name="(object._get_report_base_filename())" /> - - - - -