139 lines
5.2 KiB
Python
139 lines
5.2 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
from collections import defaultdict
|
|
from datetime import datetime
|
|
import re
|
|
|
|
from odoo import api, models
|
|
from odoo.tools import SQL
|
|
|
|
|
|
class KpiProvider(models.AbstractModel):
|
|
_inherit = 'kpi.provider'
|
|
|
|
@api.model
|
|
def get_account_reports_kpi_summary(self):
|
|
return get_kpi_summary(self.env.cr, self.env.uid)
|
|
|
|
@api.model
|
|
def get_kpi_summary(self):
|
|
result = super().get_kpi_summary()
|
|
result.extend(self.get_account_reports_kpi_summary())
|
|
return result
|
|
|
|
|
|
def _tax_return_grouping_key(external_ids, return_info):
|
|
external_id = ''
|
|
|
|
# Determine the external identifier used to categorize the tax returns:
|
|
# - use the identifier of the root report if any,
|
|
# - otherwise use the identifier of the report,
|
|
# - otherwise use the identifier of the return type.
|
|
external_id = external_ids.get(('account.report', return_info['root_report_id']))
|
|
if not external_id:
|
|
external_id = external_ids.get(('account.report', return_info['report_id']))
|
|
if not external_id:
|
|
external_id = external_ids.get(('account.return.type', return_info['return_type_id']))
|
|
if not external_id:
|
|
external_id = return_info['tax_return_name']
|
|
|
|
# Special case for l10n_eu_oss_reports.* that are all merged together.
|
|
if external_id.startswith('l10n_eu_oss_reports.'):
|
|
external_id = 'l10n_eu_oss_reports'
|
|
|
|
# property names must match odoo.orm.utils.regex_alphanumeric
|
|
return re.sub(r'[^a-z0-9_]', '_', external_id.lower())
|
|
|
|
|
|
def _tax_returns_get_value(tax_returns):
|
|
for tax_return in tax_returns:
|
|
if tax_return['is_late']:
|
|
return 'late'
|
|
elif not tax_return['is_completed']:
|
|
if tax_return['state'] in ('reviewed', 'submitted'):
|
|
return 'to_submit'
|
|
elif tax_return['in_next_3_months']:
|
|
return 'to_do'
|
|
else:
|
|
return 'longterm'
|
|
return 'done'
|
|
|
|
|
|
def get_kpi_summary(cr, uid):
|
|
"""
|
|
Retrieve the status of pending tax returns grouped by report or return type.
|
|
|
|
The returned KPIs include:
|
|
- overdue tax returns;
|
|
- upcoming tax returns within the next 3 months;
|
|
- completion state of each tax return group.
|
|
|
|
This function intentionally bypasses the ORM so KPI summaries can be retrieved
|
|
without loading a registry, allowing multi-database servers to serve them faster.
|
|
"""
|
|
expected_columns = {
|
|
'account_return.date_deadline',
|
|
'account_return.is_completed',
|
|
'account_return.type_id',
|
|
'account_return_type.report_id',
|
|
'account_report.root_report_id',
|
|
}
|
|
cr.execute(SQL("""
|
|
SELECT table_name || '.' || column_name
|
|
FROM information_schema.columns
|
|
WHERE table_name || '.' || column_name IN %(columns)s
|
|
""", columns=tuple(expected_columns)))
|
|
existing_columns = {x[0] for x in cr.fetchall()}
|
|
if expected_columns - existing_columns:
|
|
# Needed columns are not present -> module is not installed
|
|
return []
|
|
|
|
cr.execute(SQL("""
|
|
SELECT model, res_id, module || '.' || name
|
|
FROM ir_model_data
|
|
WHERE model IN ('account.report', 'account.return.type')
|
|
AND module != '__export__'
|
|
"""))
|
|
external_ids = {
|
|
(model, res_id): complete_name
|
|
for model, res_id, complete_name in cr.fetchall()
|
|
}
|
|
|
|
now = datetime.now() # Using python-generated dates to respect what freezegun has frozen in tests
|
|
cr.execute(SQL("""
|
|
SELECT return.id return_id,
|
|
type.id return_type_id,
|
|
report.id report_id,
|
|
root_report.id root_report_id,
|
|
COALESCE(report.name->>partner.lang,
|
|
report.name->>'en_US',
|
|
type.name->>partner.lang,
|
|
type.name->>'en_US') tax_return_name,
|
|
return.date_deadline <= (%(now)s AT TIME ZONE COALESCE(partner.tz, 'UTC'))::date is_late,
|
|
return.date_deadline <= (%(now)s AT TIME ZONE COALESCE(partner.tz, 'UTC'))::date + INTERVAL '3 months' in_next_3_months,
|
|
return.is_completed,
|
|
return.state
|
|
FROM account_return return
|
|
JOIN res_users u ON u.id = %(uid)s
|
|
JOIN res_partner partner ON partner.id = u.partner_id
|
|
JOIN account_return_type type ON return.type_id = type.id
|
|
LEFT JOIN account_report report ON type.report_id = report.id
|
|
LEFT JOIN account_report root_report ON report.root_report_id = root_report.id
|
|
WHERE NOT return.is_completed
|
|
OR return.date_deadline >= (%(now)s AT TIME ZONE COALESCE(partner.tz, 'UTC'))::date
|
|
ORDER BY return.date_deadline NULLS LAST, return.id
|
|
""",
|
|
uid=uid,
|
|
now=now,
|
|
))
|
|
returns_by_external_id = defaultdict(list)
|
|
for return_info in cr.dictfetchall():
|
|
key = _tax_return_grouping_key(external_ids, return_info)
|
|
returns_by_external_id[key].append(return_info)
|
|
|
|
return [{
|
|
'id': f'account_return.{external_id}',
|
|
'name': tax_returns[0]['tax_return_name'],
|
|
'type': 'return_status',
|
|
'value': _tax_returns_get_value(tax_returns),
|
|
} for external_id, tax_returns in returns_by_external_id.items()]
|