# pylint: disable=C0326 from .common import TestAccountReportsCommon from odoo import Command, fields from odoo.tests import tagged import json @tagged('post_install', '-at_install') class TestPartnerLedgerReport(TestAccountReportsCommon): @classmethod def setUpClass(cls): super().setUpClass() cls.partner_category_a = cls.env['res.partner.category'].create({'name': 'partner_categ_a'}) cls.partner_category_b = cls.env['res.partner.category'].create({'name': 'partner_categ_b'}) cls.partner_a.write({'category_id': [Command.set([cls.partner_category_a.id, cls.partner_category_b.id])]}) cls.partner_b.write({'category_id': [Command.set([cls.partner_category_a.id])]}) cls.partner_c = cls._create_partner(name='partner_c', category_id=[Command.set([cls.partner_category_b.id])]) # Entries in 2016 for company_1 to test the initial balance. cls.move_2016_1 = cls.env['account.move'].create({ 'move_type': 'entry', 'date': fields.Date.from_string('2016-01-01'), 'journal_id': cls.company_data['default_journal_misc'].id, 'line_ids': [ (0, 0, {'debit': 100.0, 'credit': 0.0, 'name': '2016_1_1', 'account_id': cls.company_data['default_account_payable'].id, 'partner_id': cls.partner_a.id}), (0, 0, {'debit': 200.0, 'credit': 0.0, 'name': '2016_1_1', 'account_id': cls.company_data['default_account_payable'].id, 'partner_id': cls.partner_b.id}), (0, 0, {'debit': 0.0, 'credit': 300.0, 'name': '2016_1_2', 'account_id': cls.company_data['default_account_receivable'].id, 'partner_id': cls.partner_c.id}), ], }) cls.move_2016_1.action_post() # Entries in 2016 for company_2 to test the initial balance in multi-companies/multi-currencies. cls.move_2016_2 = cls.env['account.move'].create({ 'move_type': 'entry', 'date': fields.Date.from_string('2016-06-01'), 'journal_id': cls.company_data_2['default_journal_misc'].id, 'line_ids': [ (0, 0, {'debit': 100.0, 'credit': 0.0, 'name': '2016_2_1', 'account_id': cls.company_data_2['default_account_payable'].id, 'partner_id': cls.partner_a.id}), (0, 0, {'debit': 0.0, 'credit': 100.0, 'name': '2016_2_2', 'account_id': cls.company_data_2['default_account_receivable'].id, 'partner_id': cls.partner_c.id}), ], }) cls.move_2016_2.action_post() # Entry in 2017 for company_1 to test the report at current date. cls.move_2017_1 = cls.env['account.move'].create({ 'move_type': 'entry', 'date': fields.Date.from_string('2017-01-01'), 'journal_id': cls.company_data['default_journal_misc'].id, 'line_ids': [ (0, 0, {'debit': 1000.0, 'credit': 0.0, 'name': '2017_1_1', 'account_id': cls.company_data['default_account_payable'].id, 'partner_id': cls.partner_b.id}), (0, 0, {'debit': 2000.0, 'credit': 0.0, 'name': '2017_1_2', 'account_id': cls.company_data['default_account_payable'].id, 'partner_id': cls.partner_a.id}), (0, 0, {'debit': 3000.0, 'credit': 0.0, 'name': '2017_1_3', 'account_id': cls.company_data['default_account_payable'].id, 'partner_id': cls.partner_a.id}), (0, 0, {'debit': 4000.0, 'credit': 0.0, 'name': '2017_1_4', 'account_id': cls.company_data['default_account_receivable'].id, 'partner_id': cls.partner_a.id}), (0, 0, {'debit': 5000.0, 'credit': 0.0, 'name': '2017_1_5', 'account_id': cls.company_data['default_account_receivable'].id, 'partner_id': cls.partner_a.id}), (0, 0, {'debit': 6000.0, 'credit': 0.0, 'name': '2017_1_6', 'account_id': cls.company_data['default_account_receivable'].id, 'partner_id': cls.partner_a.id}), (0, 0, {'debit': 0.0, 'credit': 6000.0, 'name': '2017_1_7', 'account_id': cls.company_data['default_account_receivable'].id, 'partner_id': cls.partner_c.id}), (0, 0, {'debit': 0.0, 'credit': 7000.0, 'name': '2017_1_8', 'account_id': cls.company_data['default_account_receivable'].id, 'partner_id': cls.partner_c.id}), (0, 0, {'debit': 0.0, 'credit': 8000.0, 'name': '2017_1_9', 'account_id': cls.company_data['default_account_receivable'].id, 'partner_id': cls.partner_c.id}), ], }) cls.move_2017_1.action_post() # Entry in 2017 for company_2 to test the current period in multi-companies/multi-currencies. cls.move_2017_2 = cls.env['account.move'].create({ 'move_type': 'entry', 'date': fields.Date.from_string('2017-06-01'), 'journal_id': cls.company_data_2['default_journal_misc'].id, 'line_ids': [ (0, 0, {'debit': 400.0, 'credit': 0.0, 'name': '2017_2_1', 'account_id': cls.company_data_2['default_account_receivable'].id}), (0, 0, {'debit': 0.0, 'credit': 400.0, 'name': '2017_2_2', 'account_id': cls.company_data_2['default_account_receivable'].id}), ], }) cls.move_2017_2.action_post() cls.report = cls.env.ref('account_reports.partner_ledger_report') def test_partner_ledger_send_recipients_multi_currency(self): ''' Computing the recipients when sending the report by email runs the sums query, which joins the currency table. In a multi-currency setup that table is a temporary table; make sure it is created first so the query does not crash with a missing "account_currency_table" relation. ''' report = self.report.with_context( allowed_company_ids=(self.company_data['company'] + self.company_data_2['company']).ids ) options = self._generate_options(report, fields.Date.from_string('2017-01-01'), fields.Date.from_string('2017-12-31')) # Two companies with different currencies must use the (temporary) currency table. self.assertNotEqual(options['currency_table']['type'], 'monocurrency') # Without initializing the currency table this raises psycopg2.errors.UndefinedTable. recipients = report._get_report_send_recipients(options) self.assertEqual( sorted(recipients.mapped('name')), ['partner_a', 'partner_b', 'partner_c'], ) def test_partner_ledger_unfold(self): ''' Test unfolding a line when rendering the whole report. ''' options = self._generate_options(self.report, fields.Date.from_string('2017-01-01'), fields.Date.from_string('2017-12-31')) self.assertLinesValues( self.report._get_lines(options), # Name Debit Credit Balance [ 0, 6, 7, 9], [ ('partner_a', 20000.0, 0.0, 20150.0), ('partner_b', 1000.0, 0.0, 1200.0), ('partner_c', 0.0, 21000.0, -21350.0), ('Unknown Partner', 200.0, 200.0, 0.0), ('Total', 21200.0, 21200.0, 0.0), ], options, ) options['unfolded_lines'] = [self.report._get_generic_line_id('res.partner', self.partner_a.id)] self.assertLinesValues( self.report._get_lines(options), # Name Debit Credit Balance [ 0, 6, 7, 9], [ ('partner_a', 20000.0, 0.0, 20150.0), ('Initial Balance', '', '', 150.0), ('MISC/2017/01/0001 2017_1_2', 2000.0, 0.0, 2150.0), ('MISC/2017/01/0001 2017_1_3', 3000.0, 0.0, 5150.0), ('MISC/2017/01/0001 2017_1_4', 4000.0, 0.0, 9150.0), ('MISC/2017/01/0001 2017_1_5', 5000.0, 0.0, 14150.0), ('MISC/2017/01/0001 2017_1_6', 6000.0, 0.0, 20150.0), ('Total partner_a', 20000.0, 0.0, 20150.0), ('partner_b', 1000.0, 0.0, 1200.0), ('partner_c', 0.0, 21000.0, -21350.0), ('Unknown Partner', 200.0, 200.0, 0.0), ('Total', 21200.0, 21200.0, 0.0), ], options, ) def test_partner_ledger_batch_data_generator(self): """ Test the custom_unfold_all_batch_data_generator """ options = self._generate_options( self.report, fields.Date.from_string('2017-01-01'), fields.Date.from_string('2017-12-31'), default_options={'unfold_all': True} ) self.assertLinesValues( self.report._get_lines(options), # Name Debit Credit Balance [ 0, 6, 7, 9], [ ('partner_a', 20000.0, 0.0, 20150.0), ('Initial Balance', '', '', 150.0), ('MISC/2017/01/0001 2017_1_2', 2000.0, 0.0, 2150.0), ('MISC/2017/01/0001 2017_1_3', 3000.0, 0.0, 5150.0), ('MISC/2017/01/0001 2017_1_4', 4000.0, 0.0, 9150.0), ('MISC/2017/01/0001 2017_1_5', 5000.0, 0.0, 14150.0), ('MISC/2017/01/0001 2017_1_6', 6000.0, 0.0, 20150.0), ('Total partner_a', 20000.0, 0.0, 20150.0), ('partner_b', 1000.0, 0.0, 1200.0), ('Initial Balance', '', '', 200.0), ('MISC/2017/01/0001 2017_1_1', 1000.0, 0.0, 1200.0), ('Total partner_b', 1000.0, 0.0, 1200.0), ('partner_c', 0.0, 21000.0, -21350.0), ('Initial Balance', '', '', -350.0), ('MISC/2017/01/0001 2017_1_7', 0.0, 6000.0, -6350.0), ('MISC/2017/01/0001 2017_1_8', 0.0, 7000.0, -13350.0), ('MISC/2017/01/0001 2017_1_9', 0.0, 8000.0, -21350.0), ('Total partner_c', 0.0, 21000.0, -21350.0), ('Unknown Partner', 200.0, 200.0, 0.0), ('MISC/2017/06/0001 2017_2_1', 200.0, 0.0, 200.0), ('MISC/2017/06/0001 2017_2_2', 0.0, 200.0, 0.0), ('Total Unknown Partner', 200.0, 200.0, 0.0), ('Total', 21200.0, 21200.0, 0.0), ], options, ) def test_partner_ledger_load_more(self): ''' Test unfolding a line to use the load more. ''' self.report.load_more_limit = 2 options = self._generate_options(self.report, fields.Date.from_string('2017-01-01'), fields.Date.from_string('2017-12-31')) options['unfolded_lines'] = [self.report._get_generic_line_id('res.partner', self.partner_a.id)] report_lines = self.report._get_lines(options) self.assertLinesValues( report_lines, # Name Debit Credit Balance [ 0, 6, 7, 9], [ ('partner_a', 20000.0, 0.0, 20150.0), ('Initial Balance', '', '', 150.0), ('MISC/2017/01/0001 2017_1_2', 2000.0, 0.0, 2150.0), ('MISC/2017/01/0001 2017_1_3', 3000.0, 0.0, 5150.0), ('Load more...', '', '', ''), ('Total partner_a', 20000.0, 0.0, 20150.0), ('partner_b', 1000.0, 0.0, 1200.0), ('partner_c', 0.0, 21000.0, -21350.0), ('Unknown Partner', 200.0, 200.0, 0.0), ('Total', 21200.0, 21200.0, 0.0), ], options, ) load_more_1 = self.report.get_expanded_lines( options, report_lines[0]['id'], report_lines[4]['groupby'], '_report_expand_unfoldable_line_partner_ledger', report_lines[4]['progress'], report_lines[4]['offset'], None, ) self.assertLinesValues( load_more_1, # Name Debit Credit Balance [ 0, 6, 7, 9], [ ('MISC/2017/01/0001 2017_1_4', 4000.0, 0.0, 9150.0), ('MISC/2017/01/0001 2017_1_5', 5000.0, 0.0, 14150.0), ('Load more...', '', '', ''), ], options, ) load_more_2 = self.report.get_expanded_lines( options, report_lines[0]['id'], load_more_1[2]['groupby'], '_report_expand_unfoldable_line_partner_ledger', load_more_1[2]['progress'], load_more_1[2]['offset'], None, ) self.assertLinesValues( load_more_2, # Name Debit Credit Balance [ 0, 6, 7, 9], [ ('MISC/2017/01/0001 2017_1_6', 6000.0, 0.0, 20150.0), ], options, ) def test_partner_ledger_filter_account_types(self): ''' Test building the report with a filter on account types. When filtering on receivable accounts (i.e. trade_receivable and/or non_trade_receivable), partner_b should disappear from the report. ''' options = self._generate_options(self.report, fields.Date.from_string('2017-01-01'), fields.Date.from_string('2017-12-31')) options['unfolded_lines'] = [self.report._get_generic_line_id('res.partner', self.partner_a.id)] options = self._update_multi_selector_filter(options, 'account_type', ['non_trade_receivable', 'trade_receivable']) self.assertLinesValues( self.report._get_lines(options), # Name Debit Credit Balance [ 0, 6, 7, 9], [ ('partner_a', 15000.0, 0.0, 15000.0), ('MISC/2017/01/0001 2017_1_4', 4000.0, 0.0, 4000.0), ('MISC/2017/01/0001 2017_1_5', 5000.0, 0.0, 9000.0), ('MISC/2017/01/0001 2017_1_6', 6000.0, 0.0, 15000.0), ('Total partner_a', 15000.0, 0.0, 15000.0), ('partner_c', 0.0, 21000.0, -21350.0), ('Unknown Partner', 200.0, 200.0, 0.0), ('Total', 15200.0, 21200.0, -6350.0), ], options, ) def test_partner_ledger_filter_partners(self): ''' Test the filter on top allowing to filter on res.partner.''' options = self._generate_options(self.report, fields.Date.from_string('2017-01-01'), fields.Date.from_string('2017-12-31')) options['partner_ids'] = (self.partner_a + self.partner_c).ids self.assertLinesValues( self.report._get_lines(options), # Name Debit Credit Balance [ 0, 6, 7, 9], [ ('partner_a', 20000.0, 0.00, 20150.0), ('partner_c', 0.00, 21000.0, -21350.0), ('Total', 20000.0, 21000.0, -1200.00), ], options, ) def test_partner_ledger_filter_partner_categories(self): ''' Test the filter on top allowing to filter on res.partner.category.''' options = self._generate_options(self.report, fields.Date.from_string('2017-01-01'), fields.Date.from_string('2017-12-31')) options['partner_categories'] = self.partner_category_a.ids self.assertLinesValues( self.report._get_lines(options), # Name Debit Credit Balance [ 0, 6, 7, 9], [ ('partner_a', 20000.0, 0.0, 20150.0), ('partner_b', 1000.0, 0.0, 1200.0), ('Total', 21000.0, 0.0, 21350.0), ], options, ) def test_partner_ledger_unknown_partner(self): ''' Test the partner ledger for whenever a line appearing in it has no partner assigned. Check that reconciling this line with an invoice/bill of a partner does affect his balance. ''' options = self._generate_options(self.report, fields.Date.from_string('2017-01-01'), fields.Date.from_string('2017-12-31')) misc_move = self.env['account.move'].create({ 'date': '2017-03-31', 'line_ids': [ (0, 0, {'debit': 1000.0, 'credit': 0.0, 'account_id': self.company_data['default_account_revenue'].id}), (0, 0, {'debit': 0.0, 'credit': 1000.0, 'account_id': self.company_data['default_account_receivable'].id}), ], }) misc_move.action_post() self.assertLinesValues( self.report._get_lines(options), # Name Debit Credit Balance [ 0, 6, 7, 9], [ ('partner_a', 20000.0, 0.00, 20150.0), ('partner_b', 1000.00, 0.00, 1200.00), ('partner_c', 0.00, 21000.0, -21350.0), ('Unknown Partner', 200.00, 1200.00, -1000.00), ('Total', 21200.0, 22200.0, -1000.00), ], options, ) debit_line = self.move_2017_1.line_ids.filtered(lambda line: line.debit == 4000.0) credit_line = misc_move.line_ids.filtered(lambda line: line.credit == 1000.0) (debit_line + credit_line).reconcile() self.assertLinesValues( self.report._get_lines(options), # Name Debit Credit Balance [ 0, 6, 7, 9], [ ('partner_a', 20000.0, 1000.00, 19150.0), ('partner_b', 1000.00, 0.00, 1200.00), ('partner_c', 0.00, 21000.0, -21350.0), ('Unknown Partner', 1200.00, 1200.00, 0.00), ('Total', 22200.0, 23200.0, -1000.00), ], options, ) # Unfold 'partner_a' options['unfolded_lines'] = [self.report._get_generic_line_id('res.partner', self.partner_a.id)] self.assertLinesValues( self.report._get_lines(options), # Name Debit Credit Balance [ 0, 6, 7, 9], [ ('partner_a', 20000.0, 1000.00, 19150.0), ('Initial Balance', '', '', 150.0), ('MISC/2017/01/0001 2017_1_2', 2000.00, 0.00, 2150.0), ('MISC/2017/01/0001 2017_1_3', 3000.00, 0.00, 5150.0), ('MISC/2017/01/0001 2017_1_4', 4000.00, 0.00, 9150.0), ('MISC/2017/01/0001 2017_1_5', 5000.00, 0.00, 14150.0), ('MISC/2017/01/0001 2017_1_6', 6000.00, 0.00, 20150.0), ('MISC/2017/03/0001', 0.00, 1000.00, 19150.0), ('Total partner_a', 20000.0, 1000.00, 19150.0), ('partner_b', 1000.00, 0.00, 1200.00), ('partner_c', 0.00, 21000.0, -21350.0), ('Unknown Partner', 1200.00, 1200.00, 0.00), ('Total', 22200.0, 23200.0, -1000.00), ], options, ) # Unfold 'Unknown Partner' options['unfolded_lines'] = [self.report._get_generic_line_id('res.partner', None, markup='no_partner')] self.assertLinesValues( self.report._get_lines(options), # Name Debit Credit Balance [ 0, 6, 7, 9], [ ('partner_a', 20000.0, 1000.00, 19150.0), ('partner_b', 1000.00, 0.00, 1200.00), ('partner_c', 0.00, 21000.0, -21350.0), ('Unknown Partner', 1200.00, 1200.00, 0.00), ('MISC/2017/03/0001', 0.00, 1000.00, -1000.00), ('MISC/2017/06/0001 2017_2_1', 200.00, 0.00, -800.00), ('MISC/2017/06/0001 2017_2_2', 0.00, 200.00, -1000.00), ('MISC/2017/03/0001', 1000.00, 0.00, 0.00), ('Total Unknown Partner', 1200.00, 1200.00, 0.00), ('Total', 22200.0, 23200.0, -1000.00), ], options, ) # Change the dates to exclude the reconciliation max date: situation is back to the beginning options = self._generate_options(self.report, fields.Date.from_string('2017-01-01'), fields.Date.from_string('2017-03-30')) self.assertLinesValues( self.report._get_lines(options), # Name Debit Credit Balance [ 0, 6, 7, 9], [ ('partner_a', 20000.0, 0.00, 20150.0), ('partner_b', 1000.00, 0.00, 1200.00), ('partner_c', 0.00, 21000.0, -21350.0), ('Total', 21000.0, 21000.0, 0.00), ], options, ) # Change the dates to have a date_from > to the reconciliation max date and check the initial balances are correct options = self._generate_options(self.report, fields.Date.from_string('2017-04-01'), fields.Date.from_string('2017-04-01')) self.assertLinesValues( self.report._get_lines(options), # Name Debit Credit Balance [ 0, 6, 7, 9], [ ('partner_a', 0.0, 1000.0, 19150.0), ('partner_b', 0.0, 0.0, 1200.00), ('partner_c', 0.0, 0.0, -21350.0), ('Unknown Partner', 1000.0, 0.0, 0.0), ('Total', 1000.0, 1000.0, -1000.0), ], options, ) # Unfold 'partner_a' to check the initial balance line is correct options['unfolded_lines'] = [self.report._get_generic_line_id('res.partner', self.partner_a.id)] self.assertLinesValues( self.report._get_lines(options), # Name Debit Credit Balance [ 0, 6, 7, 9], [ ('partner_a', 0.0, 1000.0, 19150.0), ('Initial Balance', '', 1000.0, 19150.0), ('Total partner_a', 0.0, 1000.0, 19150.0), ('partner_b', 0.0, 0.0, 1200.0), ('partner_c', 0.0, 0.0, -21350.0), ('Unknown Partner', 1000.0, 0.0, 0.0), ('Total', 1000.0, 1000.0, -1000.0), ], options, ) def test_partner_ledger_prefix_groups(self): partner_names = [ 'A', 'A partner', 'A nice partner', 'A new partner', 'An original partner', 'Another partner', 'Anonymous partner', 'Annoyed partner', 'Brave partner', ] test_date = '2010-12-13' test_partners = self.env['res.partner'] for name in partner_names: partner = self.env['res.partner'].create({'name': name}) test_partners += partner invoice = self.init_invoice('out_invoice', partner=partner, invoice_date=test_date, amounts=[42.0], taxes=[], post=True) # Without prefix groups options = self._generate_options(self.report, test_date, test_date) self.assertLinesValues( self.report._get_lines(options), # Name Journal Debit Credit Balance [ 0, 1, 6, 7, 9], [ ('A', '', 42.0, 0.0, 42.0), ('A new partner', '', 42.0, 0.0, 42.0), ('A nice partner', '', 42.0, 0.0, 42.0), ('A partner', '', 42.0, 0.0, 42.0), ('An original partner', '', 42.0, 0.0, 42.0), ('Annoyed partner', '', 42.0, 0.0, 42.0), ('Anonymous partner', '', 42.0, 0.0, 42.0), ('Another partner', '', 42.0, 0.0, 42.0), ('Brave partner', '', 42.0, 0.0, 42.0), ('Total', '', 378.0, 0.0, 378.0), ], options, ) # With prefix groups self.report.prefix_groups_threshold = 3 options = self._generate_options(self.report, test_date, test_date, default_options={'unfold_all': True}) self.assertLinesValues( self.report._get_lines(options), # Name Journal Debit Credit Balance [ 0, 1, 6, 7, 9], [ ('A (8 lines)', '', 336.0, 0.0, 336.0), ('A', '', 42.0, 0.0, 42.0), ('INV/2010/00001', 'INV', 42.0, 0.0, 42.0), ('Total A', '', 42.0, 0.0, 42.0), ('A[ ] (3 lines)', '', 126.0, 0.0, 126.0), ('A N (2 lines)', '', 84.0, 0.0, 84.0), ('A new partner', '', 42.0, 0.0, 42.0), ('INV/2010/00004', 'INV', 42.0, 0.0, 42.0), ('Total A new partner', '', 42.0, 0.0, 42.0), ('A nice partner', '', 42.0, 0.0, 42.0), ('INV/2010/00003', 'INV', 42.0, 0.0, 42.0), ('Total A nice partner', '', 42.0, 0.0, 42.0), ('Total A N (2 lines)', '', 84.0, 0.0, 84.0), ('A P (1 line)', '', 42.0, 0.0, 42.0), ('A partner', '', 42.0, 0.0, 42.0), ('INV/2010/00002', 'INV', 42.0, 0.0, 42.0), ('Total A partner', '', 42.0, 0.0, 42.0), ('Total A P (1 line)', '', 42.0, 0.0, 42.0), ('Total A[ ] (3 lines)', '', 126.0, 0.0, 126.0), ('AN (4 lines)', '', 168.0, 0.0, 168.0), ('AN[ ] (1 line)', '', 42.0, 0.0, 42.0), ('An original partner', '', 42.0, 0.0, 42.0), ('INV/2010/00005', 'INV', 42.0, 0.0, 42.0), ('Total An original partner', '', 42.0, 0.0, 42.0), ('Total AN[ ] (1 line)', '', 42.0, 0.0, 42.0), ('ANN (1 line)', '', 42.0, 0.0, 42.0), ('Annoyed partner', '', 42.0, 0.0, 42.0), ('INV/2010/00008', 'INV', 42.0, 0.0, 42.0), ('Total Annoyed partner', '', 42.0, 0.0, 42.0), ('Total ANN (1 line)', '', 42.0, 0.0, 42.0), ('ANO (2 lines)', '', 84.0, 0.0, 84.0), ('Anonymous partner', '', 42.0, 0.0, 42.0), ('INV/2010/00007', 'INV', 42.0, 0.0, 42.0), ('Total Anonymous partner', '', 42.0, 0.0, 42.0), ('Another partner', '', 42.0, 0.0, 42.0), ('INV/2010/00006', 'INV', 42.0, 0.0, 42.0), ('Total Another partner', '', 42.0, 0.0, 42.0), ('Total ANO (2 lines)', '', 84.0, 0.0, 84.0), ('Total AN (4 lines)', '', 168.0, 0.0, 168.0), ('Total A (8 lines)', '', 336.0, 0.0, 336.0), ('B (1 line)', '', 42.0, 0.0, 42.0), ('Brave partner', '', 42.0, 0.0, 42.0), ('INV/2010/00009', 'INV', 42.0, 0.0, 42.0), ('Total Brave partner', '', 42.0, 0.0, 42.0), ('Total B (1 line)', '', 42.0, 0.0, 42.0), ('Total', '', 378.0, 0.0, 378.0), ], options, ) def test_filter_unreconciled_entries_only(self): new_partner = self.env['res.partner'].create({'name': 'Obiwan Kenobi'}) move_1 = self.init_invoice('out_invoice', partner=new_partner, invoice_date='2019-01-01', amounts=[1000.0], taxes=[], post=True) move_2 = self.init_invoice('out_invoice', partner=new_partner, invoice_date='2019-01-01', amounts=[5000.0], taxes=[], post=True) self.env['account.payment.register'].create({ 'payment_date': '2019-01-01', 'line_ids': move_1.line_ids.filtered(lambda l: l.display_type == 'payment_term'), 'amount': 700.0, })._create_payments() self.env['account.payment.register'].create({ 'payment_date': '2019-01-01', 'line_ids': move_2.line_ids.filtered(lambda l: l.display_type == 'payment_term'), })._create_payments() self.assertEqual(move_1.payment_state, 'partial') self.assertEqual(move_2.payment_state, move_2._get_invoice_in_payment_state()) options = self._generate_options(self.report, '2019-01-01', '2019-12-31', default_options={'partner_ids': new_partner.ids}) self.assertLinesValues( self.report._get_lines(options), # Name Debit Credit Balance [ 0, 6, 7, 9], [ ('Obiwan Kenobi', 6000.0, 5700.0, 300.0), ('Total', 6000.0, 5700.0, 300.0), ], options ) options['unreconciled'] = True self.assertLinesValues( self.report._get_lines(options), # Name Debit Credit Balance [ 0, 6, 7, 9], [ ('Obiwan Kenobi', 1000.0, 700.0, 300.0), ('Total', 1000.0, 700.0, 300.0), ], options ) def test_print_pdf_exclude_partner_with_name_similar_to_another_partner_email(self): """ This test verifies that when printing a PDF report, the report accurately reflects the data displayed on the view by excluding partners whose email addresses are similar to other partners' names if the search bar is used to filter out partners. """ partner = self.env['res.partner'].create({'name': 'Great Customer', 'email': 'partner_a@test.com'}) self.init_invoice('out_invoice', partner=partner, invoice_date='2019-02-14', amounts=[1000.0], taxes=[], post=True) options = self._generate_options(self.report, '2019-02-01', '2019-02-28', default_options={ 'filter_search_bar': 'partner_a', 'export_mode': 'print', }) lines = self.report._get_lines(options) self.assertFalse(partner.name in [line['name'] for line in lines]) def test_partner_ledger_search_with_unknown_partner(self): ''' Test that the lines with no partners are included in the report when searching for "Unknown Partner" ''' options = self._generate_options(self.report, fields.Date.from_string('2017-01-01'), fields.Date.from_string('2017-12-31'), default_options={ 'filter_search_bar': 'un', 'export_mode': 'print', }) self.assertLinesValues( self.report._get_lines(options), # Name Debit Credit Balance [ 0, 6, 7, 9], [ ('Unknown Partner', 200.0, 200.0, 0.0), ('Total', 200.0, 200.0, 0.0), ], options, ) partner = self.env['res.partner'].create({'name': 'Unexpected Customer'}) self.init_invoice('out_invoice', partner=partner, invoice_date='2017-02-14', amounts=[1000.0], taxes=[], post=True) self.assertLinesValues( self.report._get_lines(options), # Name Debit Credit Balance [ 0, 6, 7, 9], [ ('Unexpected Customer', 1000.0, 0.0, 1000.0), ('Unknown Partner', 200.0, 200.0, 0.0), ('Total', 1200.0, 200.0, 1000.0), ], options, ) def test_no_amount_currency_col_in_single_currency(self): # In multi-currency, we get the amount_currency column self.assertGreater(len(self.env['res.currency'].search([])), 1) options = self._generate_options(self.report, fields.Date.from_string('2023-01-01'), fields.Date.from_string('2023-12-31')) self.assertTrue(any(col['expression_label'] == 'amount_currency' for col in options['columns'])) # Not in single-currency self.env['res.currency'].search([('name', '!=', 'USD')]).with_context(force_deactivate=True).active = False self.assertEqual(len(self.env['res.currency'].search([])), 1) options = self._generate_options(self.report, fields.Date.from_string('2023-01-01'), fields.Date.from_string('2023-12-31')) self.assertFalse(any(col['expression_label'] == 'amount_currency' for col in options['columns'])) def test_partner_ledger_fully_reconcile_previous_year(self): """ Verify the report's behavior when a partner is fully reconciled and has a zero balance. If the partner's balance is zero: If the partner has any unreconciled items, display the partner's information in the report. If the partner has no unreconciled items but has an initial balance > 0, show the partner. If the partner has no unreconciled items and has an initial balance = 0, hide the partner. """ new_partner = self.env['res.partner'].create({'name': 'Anakin Skywalker'}) move = self.env['account.move'].create({ 'move_type': 'out_invoice', 'invoice_date': '2019-01-01', 'partner_id': new_partner.id, 'invoice_line_ids': [Command.create({ 'quantity': 1, 'price_unit': 500.0, 'tax_ids': [], })] }) move.action_post() payment_1 = self.env['account.move'].create({ 'move_type': 'entry', 'date': '2019-01-01', 'journal_id': self.company_data['default_journal_misc'].id, 'partner_id': new_partner.id, 'line_ids': [ Command.create({'debit': 0.0, 'credit': 500.0, 'account_id': self.company_data['default_account_receivable'].id}), Command.create({'debit': 500.0, 'credit': 0.0, 'account_id': self.company_data['default_journal_bank'].default_account_id.id}), ], }) payment_1.action_post() options = self._generate_options(self.report, '2020-01-01', '2020-12-31', default_options={'partner_ids': new_partner.ids}) # Balance is 0 but there are unreconciled entries, so we show the line self.assertLinesValues( self.report._get_lines(options), # Name Debit Credit Balance [ 0, 6, 7, 9], [ ('Total', 0.0, 0.0, 0.0), ], options, ) (payment_1 + move).line_ids.filtered( lambda line: line.account_id == self.company_data['default_account_receivable'] ).reconcile() # Balance is still 0 and there is no more unreconciled entries, the partner is hide from the report self.assertLinesValues( self.report._get_lines(options), # Name Debit Credit Balance [ 0, 6, 7, 9], [ ('Total', 0.0, 0.0, 0.0), ], options, ) def test_partner_ledger_fully_reconcile_current_year(self): new_partner = self.env['res.partner'].create({'name': 'Darth Vader'}) move = self.env['account.move'].create({ 'move_type': 'out_invoice', 'invoice_date': '2019-01-01', 'partner_id': new_partner.id, 'invoice_line_ids': [Command.create({ 'quantity': 1, 'price_unit': 500.0, 'tax_ids': [], })] }) move.action_post() payment_1 = self.env['account.move'].create({ 'move_type': 'entry', 'date': '2019-01-01', 'journal_id': self.company_data['default_journal_misc'].id, 'partner_id': new_partner.id, 'line_ids': [ Command.create({'debit': 0.0, 'credit': 500.0, 'account_id': self.company_data['default_account_receivable'].id}), Command.create({'debit': 500.0, 'credit': 0.0, 'account_id': self.company_data['default_journal_bank'].default_account_id.id}), ], }) payment_1.action_post() options = self._generate_options(self.report, '2019-01-01', '2019-12-31', default_options={'partner_ids': new_partner.ids}) # Balance is 0 but there are unreconciled entries, so we show the line self.assertLinesValues( self.report._get_lines(options), # Name Debit Credit Balance [ 0, 6, 7, 9], [ ('Darth Vader', 500.0, 500.0, 0.0), ('Total', 500.0, 500.0, 0.0), ], options, ) (payment_1 + move).line_ids.filtered( lambda line: line.account_id == self.company_data['default_account_receivable'] ).reconcile() # Balance is still 0 and there is no more unreconciled entries, but the moves is in the current report period, # so we still show the partner in the report self.assertLinesValues( self.report._get_lines(options), # Name Debit Credit Balance [ 0, 6, 7, 9], [ ('Darth Vader', 500.0, 500.0, 0.0), ('Total', 500.0, 500.0, 0.0), ], options, ) def test_partner_ledger_toggle_followup(self): """Make sure that toggling the followup also (and only) toggles other lines of the same invoice.""" installments_payment_term = self.env['account.payment.term'].create({ 'name': "3 installments", 'line_ids': [ Command.create({'value_amount': 40, 'value': 'percent', 'nb_days': 0}), Command.create({'value_amount': 30, 'value': 'percent', 'nb_days': 30}), Command.create({'value_amount': 30, 'value': 'percent', 'nb_days': 60}), ], }) invoices = self.env['account.move'].create([ { 'move_type': 'out_invoice', 'invoice_date': fields.Date.from_string('2024-08-01'), 'partner_id': self.partner_a.id, 'invoice_line_ids': [Command.create({'quantity': 1, 'price_unit': 1000})], 'invoice_payment_term_id': installments_payment_term.id, }, { 'move_type': 'out_invoice', 'invoice_date': fields.Date.from_string('2024-08-10'), 'partner_id': self.partner_a.id, 'invoice_line_ids': [Command.create({'quantity': 1, 'price_unit': 500})], 'invoice_payment_term_id': installments_payment_term.id, }, ]) invoices.action_post() invoice_name = invoices[0].name options = self._generate_options(self.report, '2024-01-01', '2024-12-31', default_options={'unfold_all': True}) lines = self.report._get_lines(options) line_ids = [line['id'] for line in lines] invoice_1_line_ids = [line['id'] for line in lines if invoice_name in line['name']] self.assertEqual( self.env['account.partner.ledger.report.handler'].action_toggle_no_followup(invoice_1_line_ids[0], line_ids)['updated_line_ids'], invoice_1_line_ids, ) def test_reconciled_misc_without_partner_shows_with_partner_filter(self): """A misc move without a partner reconciled with a partner invoice must still appear when filtering the Partner Ledger by that partner.""" bernard = self.env['res.partner'].create({'name': 'Bernard Gagnant'}) invoice = self.init_invoice('out_invoice', partner=bernard, invoice_date='2019-03-01', amounts=[1000.0], taxes=[], post=True) misc_move = self.env['account.move'].create({ 'date': '2019-03-02', 'journal_id': self.company_data['default_journal_misc'].id, 'line_ids': [ (0, 0, {'debit': 1000.0, 'credit': 0.0, 'account_id': self.company_data['default_account_revenue'].id}), (0, 0, {'debit': 0.0, 'credit': 1000.0, 'account_id': self.company_data['default_account_receivable'].id}), ], }) misc_move.action_post() invoice_receivable = invoice.line_ids.filtered(lambda l: l.account_id == self.company_data['default_account_receivable']) misc_receivable = misc_move.line_ids.filtered(lambda l: l.account_id == self.company_data['default_account_receivable'] and not l.partner_id) (invoice_receivable + misc_receivable).reconcile() # Ensure both the invoice and the misc move appear when the report is fully unfolded. options = self._generate_options(self.report, '2019-03-01', '2019-03-31', default_options={'unfold_all': True}) lines = self.report._get_lines(options) self.assertLinesValues( lines, # Name Debit Credit Balance [ 0, 6, 7, 9], [ (bernard.name, 1000.0, 1000.0, 0.0), (invoice.name, 1000.0, 0.0, 1000.0), (misc_move.name, 0.0, 1000.0, 0.0), ('Total ' + bernard.name, 1000.0, 1000.0, 0.0), ('partner_a', 0.0, 0.0, 20150.0), ('Initial Balance', '', '', 20150.0), ('Total partner_a', 0.0, 0.0, 20150.0), ('partner_b', 0.0, 0.0, 1200.0), ('Initial Balance', '', '', 1200.0), ('Total partner_b', 0.0, 0.0, 1200.0), ('partner_c', 0.0, 0.0, -21350.0), ('Initial Balance', '', '', -21350.0), ('Total partner_c', 0.0, 0.0, -21350.0), ('Unknown Partner', 1000.0, 1000.0, 0.0), ('Initial Balance', '', '', 0.0), ('MISC/2019/03/0001', 0.0, 1000.0, -1000.0), ('MISC/2019/03/0001', 1000.0, 0.0, 0.0), ('Total Unknown Partner', 1000.0, 1000.0, 0.0), ('Total', 2000.0, 2000.0, 0.0), ], options, ) # When filtering the report by the partner, the misc move (despite having no partner) # must still appear because it is reconciled with the partner's invoice. options_partner = self._generate_options(self.report, '2019-03-01', '2019-03-31', default_options={'unfold_all': True, 'partner_ids': bernard.ids}) lines_filtered = self.report._get_lines(options_partner) self.assertLinesValues( lines_filtered, # Name Debit Credit Balance [ 0, 6, 7, 9], [ (bernard.name, 1000.0, 1000.0, 0.0), (invoice.name, 1000.0, 0.0, 1000.0), (misc_move.name, 0.0, 1000.0, 0.0), ('Total ' + bernard.name, 1000.0, 1000.0, 0.0), ('Total', 1000.0, 1000.0, 0.0), ], options_partner, ) def test_partner_ledger_without_filter_partner(self): """"Test opening the partner ledger report when filter_partner is disabled.""" self.report.filter_partner = False options = self._generate_options( self.report, '2019-03-01', '2019-03-31', default_options={'unfold_all': True} ) self.assertNotIn('partner_ids', options) report_lines = self.report._get_lines(options) self.assertLinesValues( report_lines, # Name Debit Credit Balance [ 0, 6, 7, 9], [ ('partner_a', 0.0, 0.0, 20150.0), ('Initial Balance', '', '', 20150.0), ('Total partner_a', 0.0, 0.0, 20150.0), ('partner_b', 0.0, 0.0, 1200.0), ('Initial Balance', '', '', 1200.0), ('Total partner_b', 0.0, 0.0, 1200.0), ('partner_c', 0.0, 0.0, -21350.0), ('Initial Balance', '', '', -21350.0), ('Total partner_c', 0.0, 0.0, -21350.0), ('Total', 0.0, 0.0, 0.0), ], options, ) def test_partner_ledger_amount_currency(self): """ Ensure the partner ledger (and its variants) correctly display the amount currency for the partner line in the case where all journal items of this partner share the same foreign currency per column group. Also ensure that initial balance moves aren't taken into consideration in the calculation of the amount currency. """ bernard = self.env['res.partner'].create({'name': 'Bernard Gagnant'}) eur_currency = self.env.ref('base.EUR') eur_currency.active = True self.env['res.currency.rate'].create({ 'name': '2026-12-01', 'rate': 1.0 / 2.0, 'currency_id': eur_currency.id, 'company_id': self.env.company.id, }) usd_invoice = self.init_invoice('out_invoice', partner=bernard, invoice_date='2025-12-31', amounts=[1000.0], taxes=[]) eur_invoice = self.init_invoice('out_invoice', partner=bernard, invoice_date='2026-01-01', amounts=[1000.0], taxes=[], currency=eur_currency) (usd_invoice + eur_invoice).action_post() # USD invoice from previous period; shouldn't affect amount_currency (displayed since it's the only foreign currency in this period). options = self._generate_options(self.report, '2026-01-01', '2026-01-31', default_options={'partner_ids': bernard.ids}) self.assertLinesValues( self.report._get_lines(options), # Name Debit Credit Amount Currency Balance [ 0, 6, 7, 8, 9], [ ('Bernard Gagnant', 2000.0, 0.0, 1000.0, 3000.0), ('Total', 2000.0, 0.0, '', 3000.0), ], options, currency_map={8: {'currency': eur_currency}}, ) # Test that if multiple currencies are used during the evaluated period, the amount currency shouldn't be displayed gbp_currency = self.env.ref('base.GBP') gbp_currency.active = True self.env['res.currency.rate'].create({ 'name': '2026-12-01', 'rate': 1.0 / 2.0, 'currency_id': gbp_currency.id, 'company_id': self.env.company.id, }) self.init_invoice('out_invoice', partner=bernard, invoice_date='2026-01-01', amounts=[1000.0], taxes=[], currency=gbp_currency, post=True) self.assertLinesValues( self.report._get_lines(options), # Name Debit Credit Amount Currency Balance [ 0, 6, 7, 8, 9], [ ('Bernard Gagnant', 4000.0, 0.0, '', 5000.0), ('Total', 4000.0, 0.0, '', 5000.0), ], options, ) # Test that if multiple currencies are used, but a horizontal groupby is applied so that only a single foreign # currency is used per column group, then the report should display the amount currency correctly horizontal_group = self.env['account.report.horizontal.group'].create({ 'name': 'Horizontal Group Currencies', 'report_ids': [self.report.id], 'rule_ids': [ Command.create({ 'field_name': 'currency_id', 'domain': f"[('id', 'in', {(eur_currency + gbp_currency + self.env.company.currency_id).ids})]", }), ], }) self.init_invoice('out_invoice', partner=bernard, invoice_date='2026-01-01', amounts=[1000.0], taxes=[], post=True) options = self._generate_options( self.report, '2026-01-01', '2026-01-31', default_options={ 'partner_ids': bernard.ids, 'selected_horizontal_group_id': horizontal_group.id, } ) self.assertLinesValues( self.report._get_lines(options), # [ EUR ] [ GBP ] [ USD ] # Name Amount Currency Balance Amount Currency Balance Amount Currency Balance [ 0, 8, 9, 17, 18, 26, 27], [ ('Bernard Gagnant', 1000.0, 2000.0, 1000.0, 2000.0, '', 2000.0), ('Total', '', 2000.0, '', 2000.0, '', 2000.0), ], options, currency_map={ 8: {'currency': eur_currency}, 17: {'currency': gbp_currency}, }, )