[MIG] aged_partner_balance_na to 18.0
This commit is contained in:
parent
289e8f3731
commit
e1a64337ff
4 changed files with 317 additions and 0 deletions
33
aged_partner_balance_na/__init__.py
Normal file
33
aged_partner_balance_na/__init__.py
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
from . import models
|
||||||
|
|
||||||
|
|
||||||
|
def post_init(env):
|
||||||
|
env['account.report'].search([('name', 'ilike', 'Aged % - North America')]).unlink()
|
||||||
|
new_receivable_report = env.ref('account_reports.aged_receivable_report').copy()
|
||||||
|
new_payable_report = env.ref('account_reports.aged_payable_report').copy()
|
||||||
|
new_receivable_report.line_ids.mapped('expression_ids').write({
|
||||||
|
'formula': '_report_custom_engine_aged_receivable_na'
|
||||||
|
})
|
||||||
|
new_payable_report.line_ids.mapped('expression_ids').write({'formula': '_report_custom_engine_aged_payable_na'})
|
||||||
|
new_receivable_report.write({
|
||||||
|
'name': 'Aged Receivable - North America',
|
||||||
|
'root_report_id': env.ref('account_reports.aged_receivable_report').id,
|
||||||
|
'availability_condition': 'always',
|
||||||
|
})
|
||||||
|
new_payable_report.write({
|
||||||
|
'name': 'Aged Payable - North America',
|
||||||
|
'root_report_id': env.ref('account_reports.aged_payable_report').id,
|
||||||
|
'availability_condition': 'always',
|
||||||
|
})
|
||||||
|
col_name_mapping = [
|
||||||
|
('At Date', 'Overdue'),
|
||||||
|
('1-30', '0-29'),
|
||||||
|
('31-60', '30-59'),
|
||||||
|
('61-90', '60-89'),
|
||||||
|
('91-120', '90-119'),
|
||||||
|
('Older', 'Later'),
|
||||||
|
]
|
||||||
|
for old_name, new_name in col_name_mapping:
|
||||||
|
((new_payable_report | new_receivable_report)
|
||||||
|
.mapped('column_ids')
|
||||||
|
.filtered(lambda col: col.name == old_name)).write({'name': new_name})
|
||||||
32
aged_partner_balance_na/__manifest__.py
Normal file
32
aged_partner_balance_na/__manifest__.py
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
#
|
||||||
|
# Bemade Inc.
|
||||||
|
#
|
||||||
|
# Copyright (C) 2023-June Bemade Inc. (<https://www.bemade.org>).
|
||||||
|
# Author: Marc Durepos (Contact : marc@bemade.org)
|
||||||
|
#
|
||||||
|
# This program is under the terms of the Odoo Proprietary License v1.0 (OPL-1)
|
||||||
|
# It is forbidden to publish, distribute, sublicense, or sell copies of the Software
|
||||||
|
# or modified copies of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||||
|
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||||
|
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
# DEALINGS IN THE SOFTWARE.
|
||||||
|
#
|
||||||
|
{
|
||||||
|
"name": "Aged Partner Balance (North American Style)",
|
||||||
|
"version": "18.0.1.0.0",
|
||||||
|
"summary": "Present aged partner balance as predictive rather than past due.",
|
||||||
|
"category": "Accounting",
|
||||||
|
"author": "Bemade Inc.",
|
||||||
|
"website": "http://www.bemade.org",
|
||||||
|
"license": "LGPL-3",
|
||||||
|
"depends": ["account_reports"],
|
||||||
|
"assets": {},
|
||||||
|
"installable": True,
|
||||||
|
"auto_install": False,
|
||||||
|
"post_init_hook": "post_init",
|
||||||
|
}
|
||||||
1
aged_partner_balance_na/models/__init__.py
Normal file
1
aged_partner_balance_na/models/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
from . import aged_partner_balance
|
||||||
251
aged_partner_balance_na/models/aged_partner_balance.py
Normal file
251
aged_partner_balance_na/models/aged_partner_balance.py
Normal file
|
|
@ -0,0 +1,251 @@
|
||||||
|
from odoo import models, fields
|
||||||
|
from odoo.tools import SQL
|
||||||
|
from itertools import chain
|
||||||
|
from dateutil.relativedelta import relativedelta
|
||||||
|
|
||||||
|
|
||||||
|
class AgedPartnerBalanceCustomHandler(models.AbstractModel):
|
||||||
|
_inherit = "account.aged.partner.balance.report.handler"
|
||||||
|
|
||||||
|
def _report_custom_engine_aged_receivable_na(
|
||||||
|
self,
|
||||||
|
expressions,
|
||||||
|
options,
|
||||||
|
date_scope,
|
||||||
|
current_groupby,
|
||||||
|
next_groupby,
|
||||||
|
offset=0,
|
||||||
|
limit=None,
|
||||||
|
warnings=None
|
||||||
|
):
|
||||||
|
return self._aged_partner_report_custom_engine_common_na(
|
||||||
|
options,
|
||||||
|
'asset_receivable',
|
||||||
|
current_groupby,
|
||||||
|
next_groupby,
|
||||||
|
offset=offset,
|
||||||
|
limit=limit
|
||||||
|
)
|
||||||
|
|
||||||
|
def _report_custom_engine_aged_payable_na(
|
||||||
|
self,
|
||||||
|
expressions,
|
||||||
|
options,
|
||||||
|
date_scope,
|
||||||
|
current_groupby,
|
||||||
|
next_groupby,
|
||||||
|
offset=0,
|
||||||
|
limit=None,
|
||||||
|
warnings=None
|
||||||
|
):
|
||||||
|
return self._aged_partner_report_custom_engine_common_na(
|
||||||
|
options,
|
||||||
|
'liability_payable',
|
||||||
|
current_groupby,
|
||||||
|
next_groupby,
|
||||||
|
offset=offset,
|
||||||
|
limit=limit
|
||||||
|
)
|
||||||
|
|
||||||
|
def _aged_partner_report_custom_engine_common_na(self, options, internal_type, current_groupby, next_groupby, offset=0, limit=None):
|
||||||
|
report = self.env['account.report'].browse(options['report_id'])
|
||||||
|
report._check_groupby_fields((next_groupby.split(',') if next_groupby else []) + ([current_groupby] if current_groupby else []))
|
||||||
|
|
||||||
|
def plus_days(date_obj, days):
|
||||||
|
return fields.Date.to_string(date_obj + relativedelta(days=days))
|
||||||
|
|
||||||
|
def minus_days(date_obj, days):
|
||||||
|
return fields.Date.to_string(date_obj - relativedelta(days=days))
|
||||||
|
|
||||||
|
date_to = fields.Date.from_string(options['date']['date_to'])
|
||||||
|
# North American style: show future due dates instead of past due
|
||||||
|
periods = [
|
||||||
|
(False, minus_days(date_to, 1)), # Overdue
|
||||||
|
(date_to, plus_days(date_to, 29)), # 0-29 days
|
||||||
|
(plus_days(date_to, 30), plus_days(date_to, 59)), # 30-59 days
|
||||||
|
(plus_days(date_to, 60), plus_days(date_to, 89)), # 60-89 days
|
||||||
|
(plus_days(date_to, 90), plus_days(date_to, 119)), # 90-119 days
|
||||||
|
(plus_days(date_to, 120), False), # 120+ days
|
||||||
|
]
|
||||||
|
|
||||||
|
def build_result_dict(report, query_res_lines):
|
||||||
|
rslt = {f'period{i}': 0 for i in range(len(periods))}
|
||||||
|
|
||||||
|
for query_res in query_res_lines:
|
||||||
|
for i in range(len(periods)):
|
||||||
|
period_key = f'period{i}'
|
||||||
|
rslt[period_key] += query_res[period_key]
|
||||||
|
|
||||||
|
if current_groupby == 'id':
|
||||||
|
query_res = query_res_lines[0] # We're grouping by id, so there is only 1 element in query_res_lines anyway
|
||||||
|
currency = self.env['res.currency'].browse(query_res['currency_id'][0]) if len(query_res['currency_id']) == 1 else None
|
||||||
|
rslt.update({
|
||||||
|
'invoice_date': query_res['invoice_date'][0] if len(query_res['invoice_date']) == 1 else None,
|
||||||
|
'due_date': query_res['due_date'][0] if len(query_res['due_date']) == 1 else None,
|
||||||
|
'amount_currency': query_res['amount_currency'],
|
||||||
|
'currency_id': query_res['currency_id'][0] if len(query_res['currency_id']) == 1 else None,
|
||||||
|
'currency': currency.display_name if currency else None,
|
||||||
|
'account_name': query_res['account_name'][0] if len(query_res['account_name']) == 1 else None,
|
||||||
|
'total': None,
|
||||||
|
'has_sublines': query_res['aml_count'] > 0,
|
||||||
|
|
||||||
|
# Needed by the custom_unfold_all_batch_data_generator, to speed-up unfold_all
|
||||||
|
'partner_id': query_res['partner_id'][0] if query_res['partner_id'] else None,
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
rslt.update({
|
||||||
|
'invoice_date': None,
|
||||||
|
'due_date': None,
|
||||||
|
'amount_currency': None,
|
||||||
|
'currency_id': None,
|
||||||
|
'currency': None,
|
||||||
|
'account_name': None,
|
||||||
|
'total': sum(rslt[f'period{i}'] for i in range(len(periods))),
|
||||||
|
'has_sublines': False,
|
||||||
|
})
|
||||||
|
|
||||||
|
return rslt
|
||||||
|
|
||||||
|
# Build period table using SQL class
|
||||||
|
period_table_format = ('(VALUES %s)' % ','.join("(%s, %s, %s)" for period in periods))
|
||||||
|
params = list(chain.from_iterable(
|
||||||
|
(period[0] or None, period[1] or None, i)
|
||||||
|
for i, period in enumerate(periods)
|
||||||
|
))
|
||||||
|
period_table = SQL(period_table_format, *params)
|
||||||
|
|
||||||
|
# Build query using new Odoo 18.0 methods
|
||||||
|
query = report._get_report_query(options, 'strict_range', domain=[('account_id.account_type', '=', internal_type)])
|
||||||
|
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)
|
||||||
|
|
||||||
|
always_present_groupby = SQL("period_table.period_index")
|
||||||
|
if current_groupby:
|
||||||
|
groupby_field_sql = self.env['account.move.line']._field_to_sql("account_move_line", current_groupby, query)
|
||||||
|
select_from_groupby = SQL("%s AS grouping_key,", groupby_field_sql)
|
||||||
|
groupby_clause = SQL("%s, %s", groupby_field_sql, always_present_groupby)
|
||||||
|
else:
|
||||||
|
select_from_groupby = SQL()
|
||||||
|
groupby_clause = always_present_groupby
|
||||||
|
|
||||||
|
multiplicator = -1 if internal_type == 'liability_payable' else 1
|
||||||
|
select_period_query = SQL(',').join(
|
||||||
|
SQL("""
|
||||||
|
CASE WHEN period_table.period_index = %(period_index)s
|
||||||
|
THEN %(multiplicator)s * SUM(%(balance_select)s)
|
||||||
|
ELSE 0 END AS %(column_name)s
|
||||||
|
""",
|
||||||
|
period_index=i,
|
||||||
|
multiplicator=multiplicator,
|
||||||
|
column_name=SQL.identifier(f"period{i}"),
|
||||||
|
balance_select=report._currency_table_apply_rate(SQL(
|
||||||
|
"account_move_line.balance - COALESCE(part_debit.amount, 0) + COALESCE(part_credit.amount, 0)"
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
for i in range(len(periods))
|
||||||
|
)
|
||||||
|
|
||||||
|
tail_query = report._get_engine_query_tail(offset, limit)
|
||||||
|
query = SQL(
|
||||||
|
"""
|
||||||
|
WITH period_table(date_start, date_stop, period_index) AS (%(period_table)s)
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
%(select_from_groupby)s
|
||||||
|
%(multiplicator)s * (
|
||||||
|
SUM(account_move_line.amount_currency)
|
||||||
|
- COALESCE(SUM(part_debit.debit_amount_currency), 0)
|
||||||
|
+ COALESCE(SUM(part_credit.credit_amount_currency), 0)
|
||||||
|
) AS amount_currency,
|
||||||
|
ARRAY_AGG(DISTINCT account_move_line.partner_id) AS partner_id,
|
||||||
|
ARRAY_AGG(account_move_line.payment_id) AS payment_id,
|
||||||
|
ARRAY_AGG(DISTINCT account_move_line.invoice_date) AS invoice_date,
|
||||||
|
ARRAY_AGG(DISTINCT COALESCE(account_move_line.date_maturity, account_move_line.date)) AS report_date,
|
||||||
|
ARRAY_AGG(DISTINCT %(account_code)s) AS account_name,
|
||||||
|
ARRAY_AGG(DISTINCT COALESCE(account_move_line.date_maturity, account_move_line.date)) AS due_date,
|
||||||
|
ARRAY_AGG(DISTINCT account_move_line.currency_id) AS currency_id,
|
||||||
|
COUNT(account_move_line.id) AS aml_count,
|
||||||
|
ARRAY_AGG(%(account_code)s) AS account_code,
|
||||||
|
%(select_period_query)s
|
||||||
|
|
||||||
|
FROM %(table_references)s
|
||||||
|
|
||||||
|
JOIN account_journal journal ON journal.id = account_move_line.journal_id
|
||||||
|
%(currency_table_join)s
|
||||||
|
|
||||||
|
LEFT JOIN LATERAL (
|
||||||
|
SELECT
|
||||||
|
SUM(part.amount) AS amount,
|
||||||
|
SUM(part.debit_amount_currency) AS debit_amount_currency,
|
||||||
|
part.debit_move_id
|
||||||
|
FROM account_partial_reconcile part
|
||||||
|
WHERE part.max_date <= %(date_to)s AND part.debit_move_id = account_move_line.id
|
||||||
|
GROUP BY part.debit_move_id
|
||||||
|
) part_debit ON TRUE
|
||||||
|
|
||||||
|
LEFT JOIN LATERAL (
|
||||||
|
SELECT
|
||||||
|
SUM(part.amount) AS amount,
|
||||||
|
SUM(part.credit_amount_currency) AS credit_amount_currency,
|
||||||
|
part.credit_move_id
|
||||||
|
FROM account_partial_reconcile part
|
||||||
|
WHERE part.max_date <= %(date_to)s AND part.credit_move_id = account_move_line.id
|
||||||
|
GROUP BY part.credit_move_id
|
||||||
|
) part_credit ON TRUE
|
||||||
|
|
||||||
|
JOIN period_table ON
|
||||||
|
(
|
||||||
|
period_table.date_start IS NULL
|
||||||
|
OR COALESCE(account_move_line.date_maturity, account_move_line.date) >= DATE(period_table.date_start)
|
||||||
|
)
|
||||||
|
AND
|
||||||
|
(
|
||||||
|
period_table.date_stop IS NULL
|
||||||
|
OR COALESCE(account_move_line.date_maturity, account_move_line.date) <= DATE(period_table.date_stop)
|
||||||
|
)
|
||||||
|
|
||||||
|
WHERE %(search_condition)s
|
||||||
|
|
||||||
|
GROUP BY %(groupby_clause)s
|
||||||
|
|
||||||
|
HAVING
|
||||||
|
ROUND(SUM(%(having_debit)s), %(currency_precision)s) != 0
|
||||||
|
OR ROUND(SUM(%(having_credit)s), %(currency_precision)s) != 0
|
||||||
|
|
||||||
|
ORDER BY %(groupby_clause)s
|
||||||
|
|
||||||
|
%(tail_query)s
|
||||||
|
""",
|
||||||
|
account_code=account_code,
|
||||||
|
period_table=period_table,
|
||||||
|
select_from_groupby=select_from_groupby,
|
||||||
|
select_period_query=select_period_query,
|
||||||
|
multiplicator=multiplicator,
|
||||||
|
table_references=query.from_clause,
|
||||||
|
currency_table_join=report._currency_table_aml_join(options),
|
||||||
|
date_to=date_to,
|
||||||
|
search_condition=query.where_clause,
|
||||||
|
groupby_clause=groupby_clause,
|
||||||
|
having_debit=report._currency_table_apply_rate(SQL("CASE WHEN account_move_line.balance > 0 THEN account_move_line.balance else 0 END - COALESCE(part_debit.amount, 0)")),
|
||||||
|
having_credit=report._currency_table_apply_rate(SQL("CASE WHEN account_move_line.balance < 0 THEN -account_move_line.balance else 0 END - COALESCE(part_credit.amount, 0)")),
|
||||||
|
currency_precision=self.env.company.currency_id.decimal_places,
|
||||||
|
tail_query=tail_query,
|
||||||
|
)
|
||||||
|
|
||||||
|
self._cr.execute(query)
|
||||||
|
query_res_lines = self._cr.dictfetchall()
|
||||||
|
|
||||||
|
if not current_groupby:
|
||||||
|
return build_result_dict(report, query_res_lines)
|
||||||
|
else:
|
||||||
|
rslt = []
|
||||||
|
|
||||||
|
all_res_per_grouping_key = {}
|
||||||
|
for query_res in query_res_lines:
|
||||||
|
grouping_key = query_res['grouping_key']
|
||||||
|
all_res_per_grouping_key.setdefault(grouping_key, []).append(query_res)
|
||||||
|
|
||||||
|
for grouping_key, query_res_lines in all_res_per_grouping_key.items():
|
||||||
|
rslt.append((grouping_key, build_result_dict(report, query_res_lines)))
|
||||||
|
|
||||||
|
return rslt
|
||||||
Loading…
Reference in a new issue