Tower: upload at_accounting 18.0.1.7 (via marketplace)
This commit is contained in:
771
addons/at_accounting/models/account_partner_ledger.py
Normal file
771
addons/at_accounting/models/account_partner_ledger.py
Normal file
@@ -0,0 +1,771 @@
|
|||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from odoo import api, models, _, fields
|
||||||
|
from odoo.exceptions import UserError
|
||||||
|
from odoo.osv import expression
|
||||||
|
from odoo.tools import SQL
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
|
||||||
|
class PartnerLedgerCustomHandler(models.AbstractModel):
|
||||||
|
_name = 'account.partner.ledger.report.handler'
|
||||||
|
_inherit = 'account.report.custom.handler'
|
||||||
|
_description = 'Partner Ledger Custom Handler'
|
||||||
|
|
||||||
|
def _get_custom_display_config(self):
|
||||||
|
return {
|
||||||
|
'css_custom_class': 'partner_ledger',
|
||||||
|
'components': {
|
||||||
|
'AccountReportLineCell': 'at_accounting.PartnerLedgerLineCell',
|
||||||
|
},
|
||||||
|
'templates': {
|
||||||
|
'AccountReportFilters': 'at_accounting.PartnerLedgerFilters',
|
||||||
|
'AccountReportLineName': 'at_accounting.PartnerLedgerLineName',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _dynamic_lines_generator(self, report, options, all_column_groups_expression_totals, warnings=None):
|
||||||
|
partner_lines, totals_by_column_group = self._build_partner_lines(report, options)
|
||||||
|
lines = report._regroup_lines_by_name_prefix(options, partner_lines, '_report_expand_unfoldable_line_partner_ledger_prefix_group', 0)
|
||||||
|
|
||||||
|
# Inject sequence on dynamic lines
|
||||||
|
lines = [(0, line) for line in lines]
|
||||||
|
|
||||||
|
# Report total line.
|
||||||
|
lines.append((0, self._get_report_line_total(options, totals_by_column_group)))
|
||||||
|
|
||||||
|
return lines
|
||||||
|
|
||||||
|
def _build_partner_lines(self, report, options, level_shift=0):
|
||||||
|
lines = []
|
||||||
|
|
||||||
|
totals_by_column_group = {
|
||||||
|
column_group_key: {
|
||||||
|
total: 0.0
|
||||||
|
for total in ['debit', 'credit', 'amount', 'balance']
|
||||||
|
}
|
||||||
|
for column_group_key in options['column_groups']
|
||||||
|
}
|
||||||
|
|
||||||
|
partners_results = self._query_partners(options)
|
||||||
|
|
||||||
|
search_filter = options.get('filter_search_bar', '')
|
||||||
|
accept_unknown_in_filter = search_filter.lower() in self._get_no_partner_line_label().lower()
|
||||||
|
for partner, results in partners_results:
|
||||||
|
if options['export_mode'] == 'print' and search_filter and not partner and not accept_unknown_in_filter:
|
||||||
|
# When printing and searching for a specific partner, make it so we only show its lines, not the 'Unknown Partner' one, that would be
|
||||||
|
# shown in case a misc entry with no partner was reconciled with one of the target partner's entries.
|
||||||
|
continue
|
||||||
|
|
||||||
|
partner_values = defaultdict(dict)
|
||||||
|
for column_group_key in options['column_groups']:
|
||||||
|
partner_sum = results.get(column_group_key, {})
|
||||||
|
|
||||||
|
partner_values[column_group_key]['debit'] = partner_sum.get('debit', 0.0)
|
||||||
|
partner_values[column_group_key]['credit'] = partner_sum.get('credit', 0.0)
|
||||||
|
partner_values[column_group_key]['amount'] = partner_sum.get('amount', 0.0)
|
||||||
|
partner_values[column_group_key]['balance'] = partner_sum.get('balance', 0.0)
|
||||||
|
|
||||||
|
totals_by_column_group[column_group_key]['debit'] += partner_values[column_group_key]['debit']
|
||||||
|
totals_by_column_group[column_group_key]['credit'] += partner_values[column_group_key]['credit']
|
||||||
|
totals_by_column_group[column_group_key]['amount'] += partner_values[column_group_key]['amount']
|
||||||
|
totals_by_column_group[column_group_key]['balance'] += partner_values[column_group_key]['balance']
|
||||||
|
|
||||||
|
lines.append(self._get_report_line_partners(options, partner, partner_values, level_shift=level_shift))
|
||||||
|
|
||||||
|
return lines, totals_by_column_group
|
||||||
|
|
||||||
|
def _report_expand_unfoldable_line_partner_ledger_prefix_group(self, line_dict_id, groupby, options, progress, offset, unfold_all_batch_data=None):
|
||||||
|
report = self.env['account.report'].browse(options['report_id'])
|
||||||
|
matched_prefix = report._get_prefix_groups_matched_prefix_from_line_id(line_dict_id)
|
||||||
|
|
||||||
|
prefix_domain = [('partner_id.name', '=ilike', f'{matched_prefix}%')]
|
||||||
|
if self._get_no_partner_line_label().upper().startswith(matched_prefix):
|
||||||
|
prefix_domain = expression.OR([prefix_domain, [('partner_id', '=', None)]])
|
||||||
|
|
||||||
|
expand_options = {
|
||||||
|
**options,
|
||||||
|
'forced_domain': options.get('forced_domain', []) + prefix_domain
|
||||||
|
}
|
||||||
|
parent_level = len(matched_prefix) * 2
|
||||||
|
partner_lines, dummy = self._build_partner_lines(report, expand_options, level_shift=parent_level)
|
||||||
|
|
||||||
|
for partner_line in partner_lines:
|
||||||
|
partner_line['id'] = report._build_subline_id(line_dict_id, partner_line['id'])
|
||||||
|
partner_line['parent_id'] = line_dict_id
|
||||||
|
|
||||||
|
lines = report._regroup_lines_by_name_prefix(
|
||||||
|
options,
|
||||||
|
partner_lines,
|
||||||
|
'_report_expand_unfoldable_line_partner_ledger_prefix_group',
|
||||||
|
parent_level,
|
||||||
|
matched_prefix=matched_prefix,
|
||||||
|
parent_line_dict_id=line_dict_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'lines': lines,
|
||||||
|
'offset_increment': len(lines),
|
||||||
|
'has_more': False,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _custom_options_initializer(self, report, options, previous_options):
|
||||||
|
super()._custom_options_initializer(report, options, previous_options=previous_options)
|
||||||
|
domain = []
|
||||||
|
|
||||||
|
company_ids = report.get_report_company_ids(options)
|
||||||
|
exch_code = self.env['res.company'].browse(company_ids).mapped('currency_exchange_journal_id')
|
||||||
|
if exch_code:
|
||||||
|
domain += ['!', '&', '&', '&', ('credit', '=', 0.0), ('debit', '=', 0.0), ('amount_currency', '!=', 0.0), ('journal_id', 'in', exch_code.ids)]
|
||||||
|
|
||||||
|
if options['export_mode'] == 'print' and options.get('filter_search_bar'):
|
||||||
|
domain += [
|
||||||
|
'|', ('matched_debit_ids.debit_move_id.partner_id.name', 'ilike', options['filter_search_bar']),
|
||||||
|
'|', ('matched_credit_ids.credit_move_id.partner_id.name', 'ilike', options['filter_search_bar']),
|
||||||
|
('partner_id.name', 'ilike', options['filter_search_bar']),
|
||||||
|
]
|
||||||
|
|
||||||
|
options['forced_domain'] = options.get('forced_domain', []) + domain
|
||||||
|
|
||||||
|
if self.env.user.has_group('base.group_multi_currency'):
|
||||||
|
options['multi_currency'] = True
|
||||||
|
|
||||||
|
columns_to_hide = []
|
||||||
|
options['hide_account'] = (previous_options or {}).get('hide_account', False)
|
||||||
|
columns_to_hide += ['journal_code', 'account_code', 'matching_number'] if options['hide_account'] else []
|
||||||
|
|
||||||
|
options['hide_debit_credit'] = (previous_options or {}).get('hide_debit_credit', False)
|
||||||
|
columns_to_hide += ['debit', 'credit'] if options['hide_debit_credit'] else ['amount']
|
||||||
|
|
||||||
|
options['columns'] = [col for col in options['columns'] if col['expression_label'] not in columns_to_hide]
|
||||||
|
|
||||||
|
options['buttons'].append({
|
||||||
|
'name': _('Send'),
|
||||||
|
'action': 'action_send_statements',
|
||||||
|
'sequence': 90,
|
||||||
|
'always_show': True,
|
||||||
|
})
|
||||||
|
|
||||||
|
def _custom_unfold_all_batch_data_generator(self, report, options, lines_to_expand_by_function):
|
||||||
|
partner_ids_to_expand = []
|
||||||
|
|
||||||
|
# Regular case
|
||||||
|
for line_dict in lines_to_expand_by_function.get('_report_expand_unfoldable_line_partner_ledger', []):
|
||||||
|
markup, model, model_id = self.env['account.report']._parse_line_id(line_dict['id'])[-1]
|
||||||
|
if model == 'res.partner':
|
||||||
|
partner_ids_to_expand.append(model_id)
|
||||||
|
elif markup == 'no_partner':
|
||||||
|
partner_ids_to_expand.append(None)
|
||||||
|
|
||||||
|
# In case prefix groups are used
|
||||||
|
no_partner_line_label = self._get_no_partner_line_label().upper()
|
||||||
|
partner_prefix_domains = []
|
||||||
|
for line_dict in lines_to_expand_by_function.get('_report_expand_unfoldable_line_partner_ledger_prefix_group', []):
|
||||||
|
prefix = report._get_prefix_groups_matched_prefix_from_line_id(line_dict['id'])
|
||||||
|
partner_prefix_domains.append([('name', '=ilike', f'{prefix}%')])
|
||||||
|
|
||||||
|
# amls without partners are regrouped "Unknown Partner", which is also used to create prefix groups
|
||||||
|
if no_partner_line_label.startswith(prefix):
|
||||||
|
partner_ids_to_expand.append(None)
|
||||||
|
|
||||||
|
if partner_prefix_domains:
|
||||||
|
partner_ids_to_expand += self.env['res.partner'].with_context(active_test=False).search(expression.OR(partner_prefix_domains)).ids
|
||||||
|
|
||||||
|
return {
|
||||||
|
'initial_balances': self._get_initial_balance_values(partner_ids_to_expand, options) if partner_ids_to_expand else {},
|
||||||
|
|
||||||
|
# load_more_limit cannot be passed to this call, otherwise it won't be applied per partner but on the whole result.
|
||||||
|
# We gain perf from batching, but load every result, even if the limit restricts them later.
|
||||||
|
'aml_values': self._get_aml_values(options, partner_ids_to_expand) if partner_ids_to_expand else {},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_report_send_recipients(self, options):
|
||||||
|
partners = options.get('partner_ids', [])
|
||||||
|
if not partners:
|
||||||
|
self._cr.execute(self._get_query_sums(options))
|
||||||
|
partners = [row['groupby'] for row in self._cr.dictfetchall() if row['groupby']]
|
||||||
|
return self.env['res.partner'].browse(partners)
|
||||||
|
|
||||||
|
def action_send_statements(self, options):
|
||||||
|
template = self.env.ref('at_accounting.email_template_customer_statement', False)
|
||||||
|
return {
|
||||||
|
'name': _("Send Partner Ledgers"),
|
||||||
|
'type': 'ir.actions.act_window',
|
||||||
|
'views': [[False, 'form']],
|
||||||
|
'res_model': 'account.report.send',
|
||||||
|
'target': 'new',
|
||||||
|
'context': {
|
||||||
|
'default_mail_template_id': template.id if template else False,
|
||||||
|
'default_report_options': options,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def action_open_partner(self, options, params):
|
||||||
|
dummy, record_id = self.env['account.report']._get_model_info_from_id(params['id'])
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.act_window',
|
||||||
|
'res_model': 'res.partner',
|
||||||
|
'res_id': record_id,
|
||||||
|
'views': [[False, 'form']],
|
||||||
|
'view_mode': 'form',
|
||||||
|
'target': 'current',
|
||||||
|
}
|
||||||
|
|
||||||
|
def _query_partners(self, options):
|
||||||
|
""" Executes the queries and performs all the computation.
|
||||||
|
:return: A list of tuple (partner, column_group_values) sorted by the table's model _order:
|
||||||
|
- partner is a res.parter record.
|
||||||
|
- column_group_values is a dict(column_group_key, fetched_values), where
|
||||||
|
- column_group_key is a string identifying a column group, like in options['column_groups']
|
||||||
|
- fetched_values is a dictionary containing:
|
||||||
|
- sum: {'debit': float, 'credit': float, 'balance': float}
|
||||||
|
- (optional) initial_balance: {'debit': float, 'credit': float, 'balance': float}
|
||||||
|
- (optional) lines: [line_vals_1, line_vals_2, ...]
|
||||||
|
"""
|
||||||
|
def assign_sum(row):
|
||||||
|
fields_to_assign = ['balance', 'debit', 'credit', 'amount']
|
||||||
|
if any(not company_currency.is_zero(row[field]) for field in fields_to_assign):
|
||||||
|
groupby_partners.setdefault(row['groupby'], defaultdict(lambda: defaultdict(float)))
|
||||||
|
for field in fields_to_assign:
|
||||||
|
groupby_partners[row['groupby']][row['column_group_key']][field] += row[field]
|
||||||
|
|
||||||
|
company_currency = self.env.company.currency_id
|
||||||
|
|
||||||
|
# Execute the queries and dispatch the results.
|
||||||
|
query = self._get_query_sums(options)
|
||||||
|
|
||||||
|
groupby_partners = {}
|
||||||
|
|
||||||
|
self._cr.execute(query)
|
||||||
|
for res in self._cr.dictfetchall():
|
||||||
|
assign_sum(res)
|
||||||
|
|
||||||
|
# Correct the sums per partner, for the lines without partner reconciled with a line having a partner
|
||||||
|
query = self._get_sums_without_partner(options)
|
||||||
|
|
||||||
|
self._cr.execute(query)
|
||||||
|
totals = {}
|
||||||
|
for total_field in ['debit', 'credit', 'amount', 'balance']:
|
||||||
|
totals[total_field] = {col_group_key: 0 for col_group_key in options['column_groups']}
|
||||||
|
|
||||||
|
for row in self._cr.dictfetchall():
|
||||||
|
totals['debit'][row['column_group_key']] += row['debit']
|
||||||
|
totals['credit'][row['column_group_key']] += row['credit']
|
||||||
|
totals['amount'][row['column_group_key']] += row['amount']
|
||||||
|
totals['balance'][row['column_group_key']] += row['balance']
|
||||||
|
|
||||||
|
if row['groupby'] not in groupby_partners:
|
||||||
|
continue
|
||||||
|
|
||||||
|
assign_sum(row)
|
||||||
|
|
||||||
|
if None in groupby_partners:
|
||||||
|
# Debit/credit are inverted for the unknown partner as the computation is made regarding the balance of the known partner
|
||||||
|
for column_group_key in options['column_groups']:
|
||||||
|
groupby_partners[None][column_group_key]['debit'] += totals['credit'][column_group_key]
|
||||||
|
groupby_partners[None][column_group_key]['credit'] += totals['debit'][column_group_key]
|
||||||
|
groupby_partners[None][column_group_key]['amount'] += totals['amount'][column_group_key]
|
||||||
|
groupby_partners[None][column_group_key]['balance'] -= totals['balance'][column_group_key]
|
||||||
|
|
||||||
|
# Retrieve the partners to browse.
|
||||||
|
# groupby_partners.keys() contains all account ids affected by:
|
||||||
|
# - the amls in the current period.
|
||||||
|
# - the amls affecting the initial balance.
|
||||||
|
if groupby_partners:
|
||||||
|
# Note a search is done instead of a browse to preserve the table ordering.
|
||||||
|
partners = self.env['res.partner'].with_context(active_test=False).search_fetch([('id', 'in', list(groupby_partners.keys()))], ["id", "name", "trust", "company_registry", "vat"])
|
||||||
|
else:
|
||||||
|
partners = []
|
||||||
|
|
||||||
|
# Add 'Partner Unknown' if needed
|
||||||
|
if None in groupby_partners.keys():
|
||||||
|
partners = [p for p in partners] + [None]
|
||||||
|
|
||||||
|
return [(partner, groupby_partners[partner.id if partner else None]) for partner in partners]
|
||||||
|
|
||||||
|
def _get_query_sums(self, options) -> SQL:
|
||||||
|
""" Construct a query retrieving all the aggregated sums to build the report. It includes:
|
||||||
|
- sums for all partners.
|
||||||
|
- sums for the initial balances.
|
||||||
|
:param options: The report options.
|
||||||
|
:return: query as SQL object
|
||||||
|
"""
|
||||||
|
queries = []
|
||||||
|
report = self.env.ref('at_accounting.partner_ledger_report')
|
||||||
|
|
||||||
|
# Create the currency table.
|
||||||
|
for column_group_key, column_group_options in report._split_options_per_column_group(options).items():
|
||||||
|
query = report._get_report_query(column_group_options, 'from_beginning')
|
||||||
|
queries.append(SQL(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
account_move_line.partner_id AS groupby,
|
||||||
|
%(column_group_key)s AS column_group_key,
|
||||||
|
SUM(%(debit_select)s) AS debit,
|
||||||
|
SUM(%(credit_select)s) AS credit,
|
||||||
|
SUM(%(balance_select)s) AS amount,
|
||||||
|
SUM(%(balance_select)s) AS balance
|
||||||
|
FROM %(table_references)s
|
||||||
|
%(currency_table_join)s
|
||||||
|
WHERE %(search_condition)s
|
||||||
|
GROUP BY account_move_line.partner_id
|
||||||
|
""",
|
||||||
|
column_group_key=column_group_key,
|
||||||
|
debit_select=report._currency_table_apply_rate(SQL("account_move_line.debit")),
|
||||||
|
credit_select=report._currency_table_apply_rate(SQL("account_move_line.credit")),
|
||||||
|
balance_select=report._currency_table_apply_rate(SQL("account_move_line.balance")),
|
||||||
|
table_references=query.from_clause,
|
||||||
|
currency_table_join=report._currency_table_aml_join(column_group_options),
|
||||||
|
search_condition=query.where_clause,
|
||||||
|
))
|
||||||
|
|
||||||
|
return SQL(' UNION ALL ').join(queries)
|
||||||
|
|
||||||
|
def _get_initial_balance_values(self, partner_ids, options):
|
||||||
|
queries = []
|
||||||
|
report = self.env.ref('at_accounting.partner_ledger_report')
|
||||||
|
for column_group_key, column_group_options in report._split_options_per_column_group(options).items():
|
||||||
|
# Get sums for the initial balance.
|
||||||
|
# period: [('date' <= options['date_from'] - 1)]
|
||||||
|
new_options = self._get_options_initial_balance(column_group_options)
|
||||||
|
query = report._get_report_query(new_options, 'from_beginning', domain=[('partner_id', 'in', partner_ids)])
|
||||||
|
queries.append(SQL(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
account_move_line.partner_id,
|
||||||
|
%(column_group_key)s AS column_group_key,
|
||||||
|
SUM(%(debit_select)s) AS debit,
|
||||||
|
SUM(%(credit_select)s) AS credit,
|
||||||
|
SUM(%(balance_select)s) AS amount,
|
||||||
|
SUM(%(balance_select)s) AS balance
|
||||||
|
FROM %(table_references)s
|
||||||
|
%(currency_table_join)s
|
||||||
|
WHERE %(search_condition)s
|
||||||
|
GROUP BY account_move_line.partner_id
|
||||||
|
""",
|
||||||
|
column_group_key=column_group_key,
|
||||||
|
debit_select=report._currency_table_apply_rate(SQL("account_move_line.debit")),
|
||||||
|
credit_select=report._currency_table_apply_rate(SQL("account_move_line.credit")),
|
||||||
|
balance_select=report._currency_table_apply_rate(SQL("account_move_line.balance")),
|
||||||
|
table_references=query.from_clause,
|
||||||
|
currency_table_join=report._currency_table_aml_join(column_group_options),
|
||||||
|
search_condition=query.where_clause,
|
||||||
|
))
|
||||||
|
|
||||||
|
self._cr.execute(SQL(" UNION ALL ").join(queries))
|
||||||
|
|
||||||
|
init_balance_by_col_group = {
|
||||||
|
partner_id: {column_group_key: {} for column_group_key in options['column_groups']}
|
||||||
|
for partner_id in partner_ids
|
||||||
|
}
|
||||||
|
for result in self._cr.dictfetchall():
|
||||||
|
init_balance_by_col_group[result['partner_id']][result['column_group_key']] = result
|
||||||
|
|
||||||
|
return init_balance_by_col_group
|
||||||
|
|
||||||
|
def _get_options_initial_balance(self, options):
|
||||||
|
""" Create options used to compute the initial balances for each partner.
|
||||||
|
The resulting dates domain will be:
|
||||||
|
[('date' <= options['date_from'] - 1)]
|
||||||
|
:param options: The report options.
|
||||||
|
:return: A copy of the options, modified to match the dates to use to get the initial balances.
|
||||||
|
"""
|
||||||
|
new_date_to = fields.Date.from_string(options['date']['date_from']) - timedelta(days=1)
|
||||||
|
new_date_options = dict(options['date'], date_from=False, date_to=fields.Date.to_string(new_date_to))
|
||||||
|
return dict(options, date=new_date_options)
|
||||||
|
|
||||||
|
def _get_sums_without_partner(self, options):
|
||||||
|
""" Get the sum of lines without partner reconciled with a line with a partner, grouped by partner. Those lines
|
||||||
|
should be considered as belonging to the partner for the reconciled amount as it may clear some of the partner
|
||||||
|
invoice/bill and they have to be accounted in the partner balance."""
|
||||||
|
queries = []
|
||||||
|
report = self.env.ref('at_accounting.partner_ledger_report')
|
||||||
|
for column_group_key, column_group_options in report._split_options_per_column_group(options).items():
|
||||||
|
query = report._get_report_query(column_group_options, 'from_beginning')
|
||||||
|
queries.append(SQL(
|
||||||
|
"""
|
||||||
|
SELECT
|
||||||
|
%(column_group_key)s AS column_group_key,
|
||||||
|
aml_with_partner.partner_id AS groupby,
|
||||||
|
SUM(%(debit_select)s) AS debit,
|
||||||
|
SUM(%(credit_select)s) AS credit,
|
||||||
|
SUM(%(balance_select)s) AS amount,
|
||||||
|
SUM(%(balance_select)s) AS balance
|
||||||
|
FROM %(table_references)s
|
||||||
|
JOIN account_partial_reconcile partial
|
||||||
|
ON account_move_line.id = partial.debit_move_id OR account_move_line.id = partial.credit_move_id
|
||||||
|
JOIN account_move_line aml_with_partner ON
|
||||||
|
(aml_with_partner.id = partial.debit_move_id OR aml_with_partner.id = partial.credit_move_id)
|
||||||
|
AND aml_with_partner.partner_id IS NOT NULL
|
||||||
|
%(currency_table_join)s
|
||||||
|
WHERE partial.max_date <= %(date_to)s AND %(search_condition)s
|
||||||
|
AND account_move_line.partner_id IS NULL
|
||||||
|
GROUP BY aml_with_partner.partner_id
|
||||||
|
""",
|
||||||
|
column_group_key=column_group_key,
|
||||||
|
debit_select=report._currency_table_apply_rate(SQL("CASE WHEN aml_with_partner.balance > 0 THEN 0 ELSE partial.amount END")),
|
||||||
|
credit_select=report._currency_table_apply_rate(SQL("CASE WHEN aml_with_partner.balance < 0 THEN 0 ELSE partial.amount END")),
|
||||||
|
balance_select=report._currency_table_apply_rate(SQL("-SIGN(aml_with_partner.balance) * partial.amount")),
|
||||||
|
table_references=query.from_clause,
|
||||||
|
currency_table_join=report._currency_table_aml_join(column_group_options, aml_alias=SQL("aml_with_partner")),
|
||||||
|
date_to=column_group_options['date']['date_to'],
|
||||||
|
search_condition=query.where_clause,
|
||||||
|
))
|
||||||
|
|
||||||
|
return SQL(" UNION ALL ").join(queries)
|
||||||
|
|
||||||
|
def _report_expand_unfoldable_line_partner_ledger(self, line_dict_id, groupby, options, progress, offset, unfold_all_batch_data=None):
|
||||||
|
def init_load_more_progress(line_dict):
|
||||||
|
return {
|
||||||
|
column['column_group_key']: line_col.get('no_format', 0)
|
||||||
|
for column, line_col in zip(options['columns'], line_dict['columns'])
|
||||||
|
if column['expression_label'] == 'balance'
|
||||||
|
}
|
||||||
|
|
||||||
|
report = self.env.ref('at_accounting.partner_ledger_report')
|
||||||
|
markup, model, record_id = report._parse_line_id(line_dict_id)[-1]
|
||||||
|
|
||||||
|
if model != 'res.partner':
|
||||||
|
raise UserError(_("Wrong ID for partner ledger line to expand: %s", line_dict_id))
|
||||||
|
|
||||||
|
prefix_groups_count = 0
|
||||||
|
for markup, dummy1, dummy2 in report._parse_line_id(line_dict_id):
|
||||||
|
if isinstance(markup, dict) and 'groupby_prefix_group' in markup:
|
||||||
|
prefix_groups_count += 1
|
||||||
|
level_shift = prefix_groups_count * 2
|
||||||
|
|
||||||
|
lines = []
|
||||||
|
|
||||||
|
# Get initial balance
|
||||||
|
if offset == 0:
|
||||||
|
if unfold_all_batch_data:
|
||||||
|
init_balance_by_col_group = unfold_all_batch_data['initial_balances'][record_id]
|
||||||
|
else:
|
||||||
|
init_balance_by_col_group = self._get_initial_balance_values([record_id], options)[record_id]
|
||||||
|
initial_balance_line = report._get_partner_and_general_ledger_initial_balance_line(options, line_dict_id, init_balance_by_col_group, level_shift=level_shift)
|
||||||
|
if initial_balance_line:
|
||||||
|
lines.append(initial_balance_line)
|
||||||
|
|
||||||
|
# For the first expansion of the line, the initial balance line gives the progress
|
||||||
|
progress = init_load_more_progress(initial_balance_line)
|
||||||
|
|
||||||
|
limit_to_load = report.load_more_limit + 1 if report.load_more_limit and options['export_mode'] != 'print' else None
|
||||||
|
|
||||||
|
if unfold_all_batch_data:
|
||||||
|
aml_results = unfold_all_batch_data['aml_values'][record_id]
|
||||||
|
else:
|
||||||
|
aml_results = self._get_aml_values(options, [record_id], offset=offset, limit=limit_to_load)[record_id]
|
||||||
|
|
||||||
|
has_more = False
|
||||||
|
treated_results_count = 0
|
||||||
|
next_progress = progress
|
||||||
|
for result in aml_results:
|
||||||
|
if options['export_mode'] != 'print' and report.load_more_limit and treated_results_count == report.load_more_limit:
|
||||||
|
# We loaded one more than the limit on purpose: this way we know we need a "load more" line
|
||||||
|
has_more = True
|
||||||
|
break
|
||||||
|
|
||||||
|
new_line = self._get_report_line_move_line(options, result, line_dict_id, next_progress, level_shift=level_shift)
|
||||||
|
lines.append(new_line)
|
||||||
|
next_progress = init_load_more_progress(new_line)
|
||||||
|
treated_results_count += 1
|
||||||
|
|
||||||
|
return {
|
||||||
|
'lines': lines,
|
||||||
|
'offset_increment': treated_results_count,
|
||||||
|
'has_more': has_more,
|
||||||
|
'progress': next_progress
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_additional_column_aml_values(self):
|
||||||
|
"""
|
||||||
|
Allows customization of additional fields in the partner ledger query.
|
||||||
|
|
||||||
|
This method is intended to be overridden by other modules to add custom fields
|
||||||
|
to the partner ledger query, e.g. SQL("account_move_line.date AS date,").
|
||||||
|
|
||||||
|
By default, it returns an empty SQL object.
|
||||||
|
"""
|
||||||
|
return SQL()
|
||||||
|
|
||||||
|
def _get_aml_values(self, options, partner_ids, offset=0, limit=None):
|
||||||
|
rslt = {partner_id: [] for partner_id in partner_ids}
|
||||||
|
|
||||||
|
partner_ids_wo_none = [x for x in partner_ids if x]
|
||||||
|
directly_linked_aml_partner_clauses = []
|
||||||
|
indirectly_linked_aml_partner_clause = SQL('aml_with_partner.partner_id IS NOT NULL')
|
||||||
|
if None in partner_ids:
|
||||||
|
directly_linked_aml_partner_clauses.append(SQL('account_move_line.partner_id IS NULL'))
|
||||||
|
if partner_ids_wo_none:
|
||||||
|
directly_linked_aml_partner_clauses.append(SQL('account_move_line.partner_id IN %s', tuple(partner_ids_wo_none)))
|
||||||
|
indirectly_linked_aml_partner_clause = SQL('aml_with_partner.partner_id IN %s', tuple(partner_ids_wo_none))
|
||||||
|
directly_linked_aml_partner_clause = SQL('(%s)', SQL(' OR ').join(directly_linked_aml_partner_clauses))
|
||||||
|
|
||||||
|
queries = []
|
||||||
|
journal_name = self.env['account.journal']._field_to_sql('journal', 'name')
|
||||||
|
report = self.env.ref('at_accounting.partner_ledger_report')
|
||||||
|
additional_columns = self._get_additional_column_aml_values()
|
||||||
|
for column_group_key, group_options in report._split_options_per_column_group(options).items():
|
||||||
|
query = report._get_report_query(group_options, 'strict_range')
|
||||||
|
account_alias = query.left_join(lhs_alias='account_move_line', lhs_column='account_id', rhs_table='account_account', rhs_column='id', link='account_id')
|
||||||
|
account_code = self.env['account.account']._field_to_sql(account_alias, 'code', query)
|
||||||
|
account_name = self.env['account.account']._field_to_sql(account_alias, 'name')
|
||||||
|
|
||||||
|
# For the move lines directly linked to this partner
|
||||||
|
# ruff: noqa: FURB113
|
||||||
|
queries.append(SQL(
|
||||||
|
'''
|
||||||
|
SELECT
|
||||||
|
account_move_line.id,
|
||||||
|
account_move_line.date_maturity,
|
||||||
|
account_move_line.name,
|
||||||
|
account_move_line.ref,
|
||||||
|
account_move_line.company_id,
|
||||||
|
account_move_line.account_id,
|
||||||
|
account_move_line.payment_id,
|
||||||
|
account_move_line.partner_id,
|
||||||
|
account_move_line.currency_id,
|
||||||
|
account_move_line.amount_currency,
|
||||||
|
account_move_line.matching_number,
|
||||||
|
%(additional_columns)s
|
||||||
|
COALESCE(account_move_line.invoice_date, account_move_line.date) AS invoice_date,
|
||||||
|
%(debit_select)s AS debit,
|
||||||
|
%(credit_select)s AS credit,
|
||||||
|
%(balance_select)s AS amount,
|
||||||
|
%(balance_select)s AS balance,
|
||||||
|
account_move.name AS move_name,
|
||||||
|
account_move.move_type AS move_type,
|
||||||
|
%(account_code)s AS account_code,
|
||||||
|
%(account_name)s AS account_name,
|
||||||
|
journal.code AS journal_code,
|
||||||
|
%(journal_name)s AS journal_name,
|
||||||
|
%(column_group_key)s AS column_group_key,
|
||||||
|
'directly_linked_aml' AS key,
|
||||||
|
0 AS partial_id
|
||||||
|
FROM %(table_references)s
|
||||||
|
JOIN account_move ON account_move.id = account_move_line.move_id
|
||||||
|
%(currency_table_join)s
|
||||||
|
LEFT JOIN res_company company ON company.id = account_move_line.company_id
|
||||||
|
LEFT JOIN res_partner partner ON partner.id = account_move_line.partner_id
|
||||||
|
LEFT JOIN account_journal journal ON journal.id = account_move_line.journal_id
|
||||||
|
WHERE %(search_condition)s AND %(directly_linked_aml_partner_clause)s
|
||||||
|
ORDER BY account_move_line.date, account_move_line.id
|
||||||
|
''',
|
||||||
|
additional_columns=additional_columns,
|
||||||
|
debit_select=report._currency_table_apply_rate(SQL("account_move_line.debit")),
|
||||||
|
credit_select=report._currency_table_apply_rate(SQL("account_move_line.credit")),
|
||||||
|
balance_select=report._currency_table_apply_rate(SQL("account_move_line.balance")),
|
||||||
|
account_code=account_code,
|
||||||
|
account_name=account_name,
|
||||||
|
journal_name=journal_name,
|
||||||
|
column_group_key=column_group_key,
|
||||||
|
table_references=query.from_clause,
|
||||||
|
currency_table_join=report._currency_table_aml_join(group_options),
|
||||||
|
search_condition=query.where_clause,
|
||||||
|
directly_linked_aml_partner_clause=directly_linked_aml_partner_clause,
|
||||||
|
))
|
||||||
|
|
||||||
|
# For the move lines linked to no partner, but reconciled with this partner. They will appear in grey in the report
|
||||||
|
queries.append(SQL(
|
||||||
|
'''
|
||||||
|
SELECT
|
||||||
|
account_move_line.id,
|
||||||
|
account_move_line.date_maturity,
|
||||||
|
account_move_line.name,
|
||||||
|
account_move_line.ref,
|
||||||
|
account_move_line.company_id,
|
||||||
|
account_move_line.account_id,
|
||||||
|
account_move_line.payment_id,
|
||||||
|
aml_with_partner.partner_id,
|
||||||
|
account_move_line.currency_id,
|
||||||
|
account_move_line.amount_currency,
|
||||||
|
account_move_line.matching_number,
|
||||||
|
%(additional_columns)s
|
||||||
|
COALESCE(account_move_line.invoice_date, account_move_line.date) AS invoice_date,
|
||||||
|
%(debit_select)s AS debit,
|
||||||
|
%(credit_select)s AS credit,
|
||||||
|
%(balance_select)s AS amount,
|
||||||
|
%(balance_select)s AS balance,
|
||||||
|
account_move.name AS move_name,
|
||||||
|
account_move.move_type AS move_type,
|
||||||
|
%(account_code)s AS account_code,
|
||||||
|
%(account_name)s AS account_name,
|
||||||
|
journal.code AS journal_code,
|
||||||
|
%(journal_name)s AS journal_name,
|
||||||
|
%(column_group_key)s AS column_group_key,
|
||||||
|
'indirectly_linked_aml' AS key,
|
||||||
|
partial.id AS partial_id
|
||||||
|
FROM %(table_references)s
|
||||||
|
%(currency_table_join)s,
|
||||||
|
account_partial_reconcile partial,
|
||||||
|
account_move,
|
||||||
|
account_move_line aml_with_partner,
|
||||||
|
account_journal journal
|
||||||
|
WHERE
|
||||||
|
(account_move_line.id = partial.debit_move_id OR account_move_line.id = partial.credit_move_id)
|
||||||
|
AND account_move_line.partner_id IS NULL
|
||||||
|
AND account_move.id = account_move_line.move_id
|
||||||
|
AND (aml_with_partner.id = partial.debit_move_id OR aml_with_partner.id = partial.credit_move_id)
|
||||||
|
AND %(indirectly_linked_aml_partner_clause)s
|
||||||
|
AND journal.id = account_move_line.journal_id
|
||||||
|
AND %(account_alias)s.id = account_move_line.account_id
|
||||||
|
AND %(search_condition)s
|
||||||
|
AND partial.max_date BETWEEN %(date_from)s AND %(date_to)s
|
||||||
|
ORDER BY account_move_line.date, account_move_line.id
|
||||||
|
''',
|
||||||
|
additional_columns=additional_columns,
|
||||||
|
debit_select=report._currency_table_apply_rate(SQL("CASE WHEN aml_with_partner.balance > 0 THEN 0 ELSE partial.amount END")),
|
||||||
|
credit_select=report._currency_table_apply_rate(SQL("CASE WHEN aml_with_partner.balance < 0 THEN 0 ELSE partial.amount END")),
|
||||||
|
balance_select=report._currency_table_apply_rate(SQL("-SIGN(aml_with_partner.balance) * partial.amount")),
|
||||||
|
account_code=account_code,
|
||||||
|
account_name=account_name,
|
||||||
|
journal_name=journal_name,
|
||||||
|
column_group_key=column_group_key,
|
||||||
|
table_references=query.from_clause,
|
||||||
|
currency_table_join=report._currency_table_aml_join(group_options),
|
||||||
|
indirectly_linked_aml_partner_clause=indirectly_linked_aml_partner_clause,
|
||||||
|
account_alias=SQL.identifier(account_alias),
|
||||||
|
search_condition=query.where_clause,
|
||||||
|
date_from=group_options['date']['date_from'],
|
||||||
|
date_to=group_options['date']['date_to'],
|
||||||
|
))
|
||||||
|
|
||||||
|
query = SQL(" UNION ALL ").join(SQL("(%s)", query) for query in queries)
|
||||||
|
|
||||||
|
if offset:
|
||||||
|
query = SQL('%s OFFSET %s ', query, offset)
|
||||||
|
|
||||||
|
if limit:
|
||||||
|
query = SQL('%s LIMIT %s ', query, limit)
|
||||||
|
|
||||||
|
self._cr.execute(query)
|
||||||
|
for aml_result in self._cr.dictfetchall():
|
||||||
|
if aml_result['key'] == 'indirectly_linked_aml':
|
||||||
|
|
||||||
|
# Append the line to the partner found through the reconciliation.
|
||||||
|
if aml_result['partner_id'] in rslt:
|
||||||
|
rslt[aml_result['partner_id']].append(aml_result)
|
||||||
|
|
||||||
|
# Balance it with an additional line in the Unknown Partner section but having reversed amounts.
|
||||||
|
if None in rslt:
|
||||||
|
rslt[None].append({
|
||||||
|
**aml_result,
|
||||||
|
'debit': aml_result['credit'],
|
||||||
|
'credit': aml_result['debit'],
|
||||||
|
'amount': aml_result['credit'] - aml_result['debit'],
|
||||||
|
'balance': -aml_result['balance'],
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
rslt[aml_result['partner_id']].append(aml_result)
|
||||||
|
|
||||||
|
return rslt
|
||||||
|
|
||||||
|
####################################################
|
||||||
|
# COLUMNS/LINES
|
||||||
|
####################################################
|
||||||
|
def _get_report_line_partners(self, options, partner, partner_values, level_shift=0):
|
||||||
|
company_currency = self.env.company.currency_id
|
||||||
|
|
||||||
|
partner_data = next(iter(partner_values.values()))
|
||||||
|
unfoldable = not company_currency.is_zero(partner_data.get('debit', 0) or partner_data.get('credit', 0))
|
||||||
|
column_values = []
|
||||||
|
report = self.env['account.report'].browse(options['report_id'])
|
||||||
|
for column in options['columns']:
|
||||||
|
col_expr_label = column['expression_label']
|
||||||
|
value = partner_values[column['column_group_key']].get(col_expr_label)
|
||||||
|
unfoldable = unfoldable or (col_expr_label in ('debit', 'credit', 'amount') and not company_currency.is_zero(value))
|
||||||
|
column_values.append(report._build_column_dict(value, column, options=options))
|
||||||
|
|
||||||
|
|
||||||
|
line_id = report._get_generic_line_id('res.partner', partner.id) if partner else report._get_generic_line_id('res.partner', None, markup='no_partner')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': line_id,
|
||||||
|
'name': partner is not None and (partner.name or '')[:128] or self._get_no_partner_line_label(),
|
||||||
|
'columns': column_values,
|
||||||
|
'level': 1 + level_shift,
|
||||||
|
'trust': partner.trust if partner else None,
|
||||||
|
'unfoldable': unfoldable,
|
||||||
|
'unfolded': line_id in options['unfolded_lines'] or options['unfold_all'],
|
||||||
|
'expand_function': '_report_expand_unfoldable_line_partner_ledger',
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_no_partner_line_label(self):
|
||||||
|
return _('Unknown Partner')
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _format_aml_name(self, line_name, move_ref, move_name=None):
|
||||||
|
''' Format the display of an account.move.line record. As its very costly to fetch the account.move.line
|
||||||
|
records, only line_name, move_ref, move_name are passed as parameters to deal with sql-queries more easily.
|
||||||
|
|
||||||
|
:param line_name: The name of the account.move.line record.
|
||||||
|
:param move_ref: The reference of the account.move record.
|
||||||
|
:param move_name: The name of the account.move record.
|
||||||
|
:return: The formatted name of the account.move.line record.
|
||||||
|
'''
|
||||||
|
return self.env['account.move.line']._format_aml_name(line_name, move_ref, move_name=move_name)
|
||||||
|
|
||||||
|
def _get_report_line_move_line(self, options, aml_query_result, partner_line_id, init_bal_by_col_group, level_shift=0):
|
||||||
|
if aml_query_result['payment_id']:
|
||||||
|
caret_type = 'account.payment'
|
||||||
|
else:
|
||||||
|
caret_type = 'account.move.line'
|
||||||
|
|
||||||
|
columns = []
|
||||||
|
report = self.env['account.report'].browse(options['report_id'])
|
||||||
|
for column in options['columns']:
|
||||||
|
col_expr_label = column['expression_label']
|
||||||
|
|
||||||
|
if col_expr_label not in aml_query_result:
|
||||||
|
raise UserError(_("The column '%s' is not available for this report.", col_expr_label))
|
||||||
|
|
||||||
|
col_value = aml_query_result[col_expr_label] if column['column_group_key'] == aml_query_result['column_group_key'] else None
|
||||||
|
|
||||||
|
if col_value is None:
|
||||||
|
columns.append(report._build_column_dict(None, None))
|
||||||
|
else:
|
||||||
|
currency = False
|
||||||
|
|
||||||
|
if col_expr_label == 'balance':
|
||||||
|
col_value += init_bal_by_col_group[column['column_group_key']]
|
||||||
|
|
||||||
|
if col_expr_label == 'amount_currency':
|
||||||
|
currency = self.env['res.currency'].browse(aml_query_result['currency_id'])
|
||||||
|
|
||||||
|
if currency == self.env.company.currency_id:
|
||||||
|
col_value = ''
|
||||||
|
|
||||||
|
columns.append(report._build_column_dict(col_value, column, options=options, currency=currency))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': report._get_generic_line_id('account.move.line', aml_query_result['id'], parent_line_id=partner_line_id, markup=aml_query_result['partial_id']),
|
||||||
|
'parent_id': partner_line_id,
|
||||||
|
'name': self._format_aml_name(aml_query_result['name'], aml_query_result['ref'], aml_query_result['move_name']),
|
||||||
|
'columns': columns,
|
||||||
|
'caret_options': caret_type,
|
||||||
|
'level': 3 + level_shift,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_report_line_total(self, options, totals_by_column_group):
|
||||||
|
column_values = []
|
||||||
|
report = self.env['account.report'].browse(options['report_id'])
|
||||||
|
for column in options['columns']:
|
||||||
|
col_value = totals_by_column_group[column['column_group_key']].get(column['expression_label'])
|
||||||
|
column_values.append(report._build_column_dict(col_value, column, options=options))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': report._get_generic_line_id(None, None, markup='total'),
|
||||||
|
'name': _('Total'),
|
||||||
|
'level': 1,
|
||||||
|
'columns': column_values,
|
||||||
|
}
|
||||||
|
|
||||||
|
def open_journal_items(self, options, params):
|
||||||
|
params['view_ref'] = 'account.view_move_line_tree_grouped_partner'
|
||||||
|
report = self.env['account.report'].browse(options['report_id'])
|
||||||
|
action = report.open_journal_items(options=options, params=params)
|
||||||
|
action.get('context', {}).update({'search_default_group_by_account': 0})
|
||||||
|
return action
|
||||||
Reference in New Issue
Block a user