You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
374 lines
17 KiB
374 lines
17 KiB
# © 2016 Julien Coux (Camptocamp)
|
|
# Copyright 2020 ForgeFlow S.L. (https://www.forgeflow.com)
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
|
|
|
from odoo import models, api
|
|
from odoo.tools import float_is_zero
|
|
from datetime import date, datetime, timedelta
|
|
import pandas as pd
|
|
|
|
|
|
class AgedPartnerBalanceReport(models.AbstractModel):
|
|
_name = 'report.account_financial_report.aged_partner_balance'
|
|
|
|
@api.model
|
|
def _initialize_account(self, ag_pb_data, acc_id):
|
|
ag_pb_data[acc_id] = {}
|
|
ag_pb_data[acc_id]['id'] = acc_id
|
|
ag_pb_data[acc_id]['residual'] = 0.0
|
|
ag_pb_data[acc_id]['current'] = 0.0
|
|
ag_pb_data[acc_id]['30_days'] = 0.0
|
|
ag_pb_data[acc_id]['60_days'] = 0.0
|
|
ag_pb_data[acc_id]['90_days'] = 0.0
|
|
ag_pb_data[acc_id]['120_days'] = 0.0
|
|
ag_pb_data[acc_id]['older'] = 0.0
|
|
return ag_pb_data
|
|
|
|
@api.model
|
|
def _initialize_partner(self, ag_pb_data, acc_id, prt_id):
|
|
ag_pb_data[acc_id][prt_id] = {}
|
|
ag_pb_data[acc_id][prt_id]['id'] = acc_id
|
|
ag_pb_data[acc_id][prt_id]['residual'] = 0.0
|
|
ag_pb_data[acc_id][prt_id]['current'] = 0.0
|
|
ag_pb_data[acc_id][prt_id]['30_days'] = 0.0
|
|
ag_pb_data[acc_id][prt_id]['60_days'] = 0.0
|
|
ag_pb_data[acc_id][prt_id]['90_days'] = 0.0
|
|
ag_pb_data[acc_id][prt_id]['120_days'] = 0.0
|
|
ag_pb_data[acc_id][prt_id]['older'] = 0.0
|
|
ag_pb_data[acc_id][prt_id]['move_lines'] = []
|
|
return ag_pb_data
|
|
|
|
def _get_journals_data(self, journals_ids):
|
|
journals = self.env['account.journal'].browse(journals_ids)
|
|
journals_data = {}
|
|
for journal in journals:
|
|
journals_data.update({journal.id: {'id': journal.id,
|
|
'code': journal.code}})
|
|
return journals_data
|
|
|
|
def _get_accounts_data(self, accounts_ids):
|
|
accounts = self.env['account.account'].browse(accounts_ids)
|
|
accounts_data = {}
|
|
for account in accounts:
|
|
accounts_data.update({account.id: {'id': account.id,
|
|
'code': account.code,
|
|
'name': account.name}})
|
|
return accounts_data
|
|
|
|
@api.model
|
|
def _get_move_lines_domain(self, company_id, account_ids, partner_ids,
|
|
only_posted_moves):
|
|
domain = [('account_id', 'in', account_ids),
|
|
('company_id', '=', company_id),
|
|
('reconciled', '=', False)]
|
|
if partner_ids:
|
|
domain += [('partner_id', 'in', partner_ids)]
|
|
if only_posted_moves:
|
|
domain += [('move_id.state', '=', 'posted')]
|
|
return domain
|
|
|
|
@api.model
|
|
def _calculate_amounts(self, ag_pb_data, acc_id, prt_id, residual,
|
|
due_date, date_at_object):
|
|
ag_pb_data[acc_id]['residual'] += residual
|
|
ag_pb_data[acc_id][prt_id]['residual'] += residual
|
|
today = date_at_object
|
|
if not due_date or today <= due_date:
|
|
ag_pb_data[acc_id]['current'] += residual
|
|
ag_pb_data[acc_id][prt_id]['current'] += residual
|
|
elif today <= due_date + timedelta(days=30):
|
|
ag_pb_data[acc_id]['30_days'] += residual
|
|
ag_pb_data[acc_id][prt_id]['30_days'] += residual
|
|
elif today <= due_date + timedelta(days=60):
|
|
ag_pb_data[acc_id]['60_days'] += residual
|
|
ag_pb_data[acc_id][prt_id]['60_days'] += residual
|
|
elif today <= due_date + timedelta(days=90):
|
|
ag_pb_data[acc_id]['90_days'] += residual
|
|
ag_pb_data[acc_id][prt_id]['90_days'] += residual
|
|
elif today <= due_date + timedelta(days=120):
|
|
ag_pb_data[acc_id]['120_days'] += residual
|
|
ag_pb_data[acc_id][prt_id]['120_days'] += residual
|
|
else:
|
|
ag_pb_data[acc_id]['older'] += residual
|
|
ag_pb_data[acc_id][prt_id]['older'] += residual
|
|
return ag_pb_data
|
|
|
|
def _get_account_partial_reconciled(self, company_id, date_at_object):
|
|
domain = [('max_date', '>=', date_at_object),
|
|
('company_id', '=', company_id)]
|
|
fields = ['debit_move_id', 'credit_move_id', 'amount']
|
|
accounts_partial_reconcile = \
|
|
self.env['account.partial.reconcile'].search_read(
|
|
domain=domain,
|
|
fields=fields
|
|
)
|
|
debit_amount = {}
|
|
credit_amount = {}
|
|
for account_partial_reconcile_data in accounts_partial_reconcile:
|
|
debit_move_id = account_partial_reconcile_data['debit_move_id'][0]
|
|
credit_move_id = account_partial_reconcile_data['credit_move_id'][0]
|
|
if debit_move_id not in debit_amount.keys():
|
|
debit_amount[debit_move_id] = 0.0
|
|
debit_amount[debit_move_id] += \
|
|
account_partial_reconcile_data['amount']
|
|
if credit_move_id not in credit_amount.keys():
|
|
credit_amount[credit_move_id] = 0.0
|
|
credit_amount[credit_move_id] += \
|
|
account_partial_reconcile_data['amount']
|
|
account_partial_reconcile_data.update({
|
|
'debit_move_id': debit_move_id,
|
|
'credit_move_id': credit_move_id,
|
|
})
|
|
return accounts_partial_reconcile, debit_amount, credit_amount
|
|
|
|
@api.model
|
|
def _get_new_move_lines_domain(self, new_ml_ids, account_ids, company_id,
|
|
partner_ids, only_posted_moves):
|
|
domain = [('account_id', 'in', account_ids),
|
|
('company_id', '=', company_id),
|
|
('id', 'in', new_ml_ids)]
|
|
if partner_ids:
|
|
domain += [('partner_id', 'in', partner_ids)]
|
|
if only_posted_moves:
|
|
domain += [('move_id.state', '=', 'posted')]
|
|
return domain
|
|
|
|
def _recalculate_move_lines(self, move_lines, debit_ids, credit_ids,
|
|
debit_amount, credit_amount, ml_ids,
|
|
account_ids, company_id, partner_ids,
|
|
only_posted_moves):
|
|
reconciled_ids = list(debit_ids) + list(credit_ids)
|
|
new_ml_ids = []
|
|
for reconciled_id in reconciled_ids:
|
|
if reconciled_id not in ml_ids and reconciled_id not in new_ml_ids:
|
|
new_ml_ids += [reconciled_id]
|
|
new_domain = self._get_new_move_lines_domain(new_ml_ids, account_ids,
|
|
company_id, partner_ids,
|
|
only_posted_moves)
|
|
ml_fields = [
|
|
'id', 'name', 'date', 'move_id', 'journal_id', 'account_id',
|
|
'partner_id', 'amount_residual', 'date_maturity', 'ref',
|
|
'reconciled']
|
|
new_move_lines = self.env['account.move.line'].search_read(
|
|
domain=new_domain, fields=ml_fields
|
|
)
|
|
move_lines = move_lines + new_move_lines
|
|
for move_line in move_lines:
|
|
ml_id = move_line['id']
|
|
if ml_id in debit_ids:
|
|
move_line['amount_residual'] += debit_amount[ml_id]
|
|
if ml_id in credit_ids:
|
|
move_line['amount_residual'] -= credit_amount[ml_id]
|
|
return move_lines
|
|
|
|
def _get_move_lines_data(
|
|
self, company_id, account_ids, partner_ids, date_at_object,
|
|
only_posted_moves, show_move_line_details):
|
|
domain = self._get_move_lines_domain(company_id, account_ids,
|
|
partner_ids, only_posted_moves)
|
|
ml_fields = [
|
|
'id', 'name', 'date', 'move_id', 'journal_id', 'account_id',
|
|
'partner_id', 'amount_residual', 'date_maturity', 'ref',
|
|
'reconciled']
|
|
move_lines = self.env['account.move.line'].search_read(
|
|
domain=domain, fields=ml_fields
|
|
)
|
|
ml_ids = set(pd.DataFrame(move_lines).id.to_list())
|
|
journals_ids = set()
|
|
partners_ids = set()
|
|
partners_data = {}
|
|
ag_pb_data = {}
|
|
if date_at_object < date.today():
|
|
acc_partial_rec, debit_amount, credit_amount = \
|
|
self._get_account_partial_reconciled(company_id, date_at_object)
|
|
if acc_partial_rec:
|
|
acc_partial_rec_data = pd.DataFrame(acc_partial_rec)
|
|
debit_ids = set(acc_partial_rec_data.debit_move_id.to_list())
|
|
credit_ids = set(acc_partial_rec_data.credit_move_id.to_list())
|
|
move_lines = self._recalculate_move_lines(
|
|
move_lines, debit_ids, credit_ids,
|
|
debit_amount, credit_amount, ml_ids, account_ids,
|
|
company_id, partner_ids, only_posted_moves
|
|
)
|
|
moves_lines_to_remove = []
|
|
for move_line in move_lines:
|
|
if move_line['date'] > date_at_object or \
|
|
float_is_zero(move_line['amount_residual'],
|
|
precision_digits=2):
|
|
moves_lines_to_remove.append(move_line)
|
|
if len(moves_lines_to_remove) > 0:
|
|
for move_line_to_remove in moves_lines_to_remove:
|
|
move_lines.remove(move_line_to_remove)
|
|
for move_line in move_lines:
|
|
journals_ids.add(move_line['journal_id'][0])
|
|
acc_id = move_line['account_id'][0]
|
|
if move_line['partner_id']:
|
|
prt_id = move_line['partner_id'][0]
|
|
prt_name = move_line['partner_id'][1]
|
|
else:
|
|
prt_id = 0
|
|
prt_name = ""
|
|
if prt_id not in partners_ids:
|
|
partners_data.update({
|
|
prt_id: {'id': prt_id, 'name': prt_name}
|
|
})
|
|
partners_ids.add(prt_id)
|
|
if acc_id not in ag_pb_data.keys():
|
|
ag_pb_data = self._initialize_account(ag_pb_data, acc_id)
|
|
if prt_id not in ag_pb_data[acc_id]:
|
|
ag_pb_data = self._initialize_partner(ag_pb_data, acc_id,
|
|
prt_id)
|
|
move_line_data = {}
|
|
if show_move_line_details:
|
|
move_line_data.update({
|
|
'date': move_line['date'],
|
|
'entry': move_line['move_id'][1],
|
|
'jnl_id': move_line['journal_id'][0],
|
|
'acc_id': acc_id,
|
|
'partner': prt_name,
|
|
'ref': move_line['ref'],
|
|
'due_date': move_line['date_maturity'],
|
|
'residual': move_line['amount_residual'],
|
|
})
|
|
ag_pb_data[acc_id][prt_id]['move_lines'].append(move_line_data)
|
|
ag_pb_data = self._calculate_amounts(
|
|
ag_pb_data, acc_id, prt_id, move_line['amount_residual'],
|
|
move_line['date_maturity'], date_at_object)
|
|
journals_data = self._get_journals_data(list(journals_ids))
|
|
accounts_data = self._get_accounts_data(ag_pb_data.keys())
|
|
return ag_pb_data, accounts_data, partners_data, journals_data
|
|
|
|
@api.model
|
|
def _compute_maturity_date(self, ml, date_at_object):
|
|
ml.update({
|
|
'current': 0.0,
|
|
'30_days': 0.0,
|
|
'60_days': 0.0,
|
|
'90_days': 0.0,
|
|
'120_days': 0.0,
|
|
'older': 0.0,
|
|
})
|
|
due_date = ml['due_date']
|
|
amount = ml['residual']
|
|
today = date_at_object
|
|
if not due_date or today <= due_date:
|
|
ml['current'] += amount
|
|
elif today <= due_date + timedelta(days=30):
|
|
ml['30_days'] += amount
|
|
elif today <= due_date + timedelta(days=60):
|
|
ml['60_days'] += amount
|
|
elif today <= due_date + timedelta(days=90):
|
|
ml['90_days'] += amount
|
|
elif today <= due_date + timedelta(days=120):
|
|
ml['120_days'] += amount
|
|
else:
|
|
ml['older'] += amount
|
|
|
|
def _create_account_list(
|
|
self, ag_pb_data, accounts_data, partners_data, journals_data,
|
|
show_move_line_details, date_at_oject):
|
|
aged_partner_data = []
|
|
for account in accounts_data.values():
|
|
acc_id = account['id']
|
|
account.update({
|
|
'residual': ag_pb_data[acc_id]['residual'],
|
|
'current': ag_pb_data[acc_id]['current'],
|
|
'30_days': ag_pb_data[acc_id]['30_days'],
|
|
'60_days': ag_pb_data[acc_id]['60_days'],
|
|
'90_days': ag_pb_data[acc_id]['90_days'],
|
|
'120_days': ag_pb_data[acc_id]['120_days'],
|
|
'older': ag_pb_data[acc_id]['older'],
|
|
'partners': [],
|
|
})
|
|
for prt_id in ag_pb_data[acc_id]:
|
|
if isinstance(prt_id, int):
|
|
partner = {
|
|
'name': partners_data[prt_id]['name'],
|
|
'residual': ag_pb_data[acc_id][prt_id]['residual'],
|
|
'current': ag_pb_data[acc_id][prt_id]['current'],
|
|
'30_days': ag_pb_data[acc_id][prt_id]['30_days'],
|
|
'60_days': ag_pb_data[acc_id][prt_id]['60_days'],
|
|
'90_days': ag_pb_data[acc_id][prt_id]['90_days'],
|
|
'120_days': ag_pb_data[acc_id][prt_id]['120_days'],
|
|
'older': ag_pb_data[acc_id][prt_id]['older'],
|
|
}
|
|
if show_move_line_details:
|
|
move_lines = []
|
|
for ml in ag_pb_data[acc_id][prt_id]['move_lines']:
|
|
ml.update({
|
|
'journal': journals_data[ml['jnl_id']]['code'],
|
|
'account': accounts_data[ml['acc_id']]['code'],
|
|
})
|
|
self._compute_maturity_date(ml, date_at_oject)
|
|
move_lines.append(ml)
|
|
partner.update({
|
|
'move_lines': move_lines
|
|
})
|
|
account['partners'].append(partner)
|
|
aged_partner_data.append(account)
|
|
return aged_partner_data
|
|
|
|
@api.model
|
|
def _calculate_percent(self, aged_partner_data):
|
|
for account in aged_partner_data:
|
|
if abs(account['residual']) > 0.01:
|
|
total = account['residual']
|
|
account.update({
|
|
'percent_current': abs(
|
|
round((account['current'] / total) * 100, 2)),
|
|
'percent_30_days': abs(
|
|
round((account['30_days'] / total) * 100,
|
|
2)),
|
|
'percent_60_days': abs(
|
|
round((account['60_days'] / total) * 100,
|
|
2)),
|
|
'percent_90_days': abs(
|
|
round((account['90_days'] / total) * 100,
|
|
2)),
|
|
'percent_120_days': abs(
|
|
round((account['120_days'] / total) * 100,
|
|
2)),
|
|
'percent_older': abs(
|
|
round((account['older'] / total) * 100, 2)),
|
|
})
|
|
else:
|
|
account.update({
|
|
'percent_current': 0.0,
|
|
'percent_30_days': 0.0,
|
|
'percent_60_days': 0.0,
|
|
'percent_90_days': 0.0,
|
|
'percent_120_days': 0.0,
|
|
'percent_older': 0.0,
|
|
})
|
|
return aged_partner_data
|
|
|
|
@api.multi
|
|
def _get_report_values(self, docids, data):
|
|
wizard_id = data['wizard_id']
|
|
company = self.env['res.company'].browse(data['company_id'])
|
|
company_id = data['company_id']
|
|
account_ids = data['account_ids']
|
|
partner_ids = data['partner_ids']
|
|
date_at = data['date_at']
|
|
date_at_object = datetime.strptime(date_at, '%Y-%m-%d').date()
|
|
only_posted_moves = data['only_posted_moves']
|
|
show_move_line_details = data['show_move_line_details']
|
|
ag_pb_data, accounts_data, partners_data, \
|
|
journals_data = self._get_move_lines_data(
|
|
company_id, account_ids, partner_ids, date_at_object,
|
|
only_posted_moves, show_move_line_details)
|
|
aged_partner_data = self._create_account_list(
|
|
ag_pb_data, accounts_data, partners_data, journals_data,
|
|
show_move_line_details, date_at_object)
|
|
aged_partner_data = self._calculate_percent(aged_partner_data)
|
|
return {
|
|
'doc_ids': [wizard_id],
|
|
'doc_model': 'open.items.report.wizard',
|
|
'docs': self.env['open.items.report.wizard'].browse(wizard_id),
|
|
'company_name': company.display_name,
|
|
'currency_name': company.currency_id.name,
|
|
'date_at': date_at,
|
|
'only_posted_moves': only_posted_moves,
|
|
'aged_partner_balance': aged_partner_data,
|
|
'show_move_lines_details': show_move_line_details,
|
|
}
|