# -*- coding: utf-8 -*- ############################################################################# # # Cybrosys Technologies Pvt. Ltd. # # Copyright (C) 2023-TODAY Cybrosys Technologies() # Author: Cybrosys Techno Solutions() # # You can modify it under the terms of the GNU LESSER # GENERAL PUBLIC LICENSE (LGPL v3), Version 3. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. # # You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE # (LGPL v3) along with this program. # If not, see . # ############################################################################# import time from datetime import datetime from dateutil.relativedelta import relativedelta from odoo import api, models, _ from odoo.exceptions import UserError from odoo.tools import float_is_zero class ReportAgedPartnerBalance(models.AbstractModel): _name = 'report.base_accounting_kit.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 periods = {} start = datetime.strptime(date_from, "%Y-%m-%d") date_from = datetime.strptime(date_from, "%Y-%m-%d").date() 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 = [] cr = self.env.cr user_company = self.env.company user_currency = user_company.currency_id ResCurrency = self.env['res.currency'].with_context(date=date_from) 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),) arg_list += (date_from, tuple(company_ids)) query = ''' SELECT DISTINCT l.partner_id, UPPER(res_partner.name) FROM account_move_line AS l left join res_partner on l.partner_id = res_partner.id, 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.account_type IN %s) AND ''' + reconciliation_clause + ''' AND (l.date <= %s) AND l.company_id IN %s ORDER BY UPPER(res_partner.name)''' 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 [], [], {} # 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.account_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''' 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 = ResCurrency._get_conversion_rate(line.company_id.currency_id, user_currency, line.balance) 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 += ResCurrency._get_conversion_rate( partial_line.company_id.currency_id, user_currency, partial_line.amount) for partial_line in line.matched_credit_ids: if partial_line.max_date <= date_from: line_amount -= ResCurrency._get_conversion_rate( partial_line.company_id.currency_id, user_currency, partial_line.amount) if not self.env.company.currency_id.is_zero(line_amount): undue_amounts[partner_id] += line_amount lines[partner_id].append({ 'line': line, 'amount': line_amount, 'period': 6, }) # 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.account_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''' 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): partner_id = line.partner_id.id or False if partner_id not in partners_amount: partners_amount[partner_id] = 0.0 line_amount = ResCurrency._get_conversion_rate(line.company_id.currency_id, user_currency, line.balance) 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 += ResCurrency._get_conversion_rate( partial_line.company_id.currency_id, user_currency, partial_line.amount) for partial_line in line.matched_credit_ids: if partial_line.max_date <= date_from: line_amount -= ResCurrency._get_conversion_rate( partial_line.company_id.currency_id, user_currency, partial_line.amount) if not self.env.company.currency_id.is_zero( line_amount): partners_amount[partner_id] += line_amount lines[partner_id].append({ 'line': line, 'amount': line_amount, 'period': i + 1, }) history.append(partners_amount) 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']: browsed_partner = self.env['res.partner'].browse( partner['partner_id']) values['name'] = browsed_partner.name and len( browsed_partner.name) >= 45 and browsed_partner.name[ 0:40] + '...' or browsed_partner.name values['trust'] = browsed_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 = data['form'].get('date_from', time.strftime('%Y-%m-%d')) if data['form']['result_selection'] == 'customer': account_type = ['asset_receivable'] elif data['form']['result_selection'] == 'supplier': account_type = ['liability_payable'] else: account_type = ['liability_payable', 'asset_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, }