[IMP] partner_statement: Allow to exclude accounts

This commit is contained in:
Simone Rubino
2025-05-05 18:28:29 +02:00
committed by Miquel Raïch
parent 965ea7d6ff
commit 795f9c60fe
6 changed files with 129 additions and 11 deletions

View File

@@ -52,6 +52,9 @@ class ActivityStatement(models.AbstractModel):
return title
def _initial_balance_sql_q1(self, partners, date_start, account_type):
excluded_accounts_ids = tuple(
self.env.context.get("excluded_accounts_ids", [])
) or (-1,)
return str(
self._cr.mogrify(
"""
@@ -84,6 +87,7 @@ class ActivityStatement(models.AbstractModel):
WHERE l2.date < %(date_start)s
) as pc ON pc.credit_move_id = l.id
WHERE l.partner_id IN %(partners)s
AND aa.id not in %(excluded_accounts_ids)s
AND l.date < %(date_start)s AND not l.blocked
AND m.state IN ('posted')
AND aa.account_type = %(account_type)s
@@ -156,6 +160,9 @@ class ActivityStatement(models.AbstractModel):
def _display_activity_lines_sql_q1(
self, partners, date_start, date_end, account_type
):
excluded_accounts_ids = tuple(
self.env.context.get("excluded_accounts_ids", [])
) or (-1,)
payment_ref = _("Payment")
return str(
self._cr.mogrify(
@@ -191,6 +198,7 @@ class ActivityStatement(models.AbstractModel):
JOIN account_move m ON (l.move_id = m.id)
JOIN account_journal aj ON (l.journal_id = aj.id)
WHERE l.partner_id IN %(partners)s
AND aa.id not in %(excluded_accounts_ids)s
AND %(date_start)s <= l.date
AND l.date <= %(date_end)s
AND m.state IN ('posted')

View File

@@ -26,6 +26,9 @@ class OutstandingStatement(models.AbstractModel):
def _display_outstanding_lines_sql_q1(self, partners, date_end, account_type):
partners = tuple(partners)
excluded_accounts_ids = tuple(
self.env.context.get("excluded_accounts_ids", [])
) or (-1,)
return str(
self._cr.mogrify(
"""
@@ -71,6 +74,7 @@ class OutstandingStatement(models.AbstractModel):
WHERE l2.date <= %(date_end)s
) as pc ON pc.credit_move_id = l.id
WHERE l.partner_id IN %(partners)s
AND aa.id not in %(excluded_accounts_ids)s
AND (
(pd.id IS NOT NULL AND
pd.max_date <= %(date_end)s) OR

View File

@@ -59,6 +59,9 @@ class ReportStatementCommon(models.AbstractModel):
return {}
def _show_buckets_sql_q1(self, partners, date_end, account_type):
excluded_accounts_ids = tuple(
self.env.context.get("excluded_accounts_ids", [])
) or (-1,)
return str(
self._cr.mogrify(
"""
@@ -91,18 +94,18 @@ class ReportStatementCommon(models.AbstractModel):
WHERE l2.date <= %(date_end)s
) as pc ON pc.credit_move_id = l.id
WHERE l.partner_id IN %(partners)s
AND (
(pd.id IS NOT NULL AND
pd.max_date <= %(date_end)s) OR
(pc.id IS NOT NULL AND
pc.max_date <= %(date_end)s) OR
(pd.id IS NULL AND pc.id IS NULL)
) AND l.date <= %(date_end)s AND not l.blocked
AND m.state IN ('posted')
AND aa.account_type = %(account_type)s
AND aa.id not in %(excluded_accounts_ids)s
AND (
(pd.id IS NOT NULL AND
pd.max_date <= %(date_end)s) OR
(pc.id IS NOT NULL AND
pc.max_date <= %(date_end)s) OR
(pd.id IS NULL AND pc.id IS NULL)
) AND l.date <= %(date_end)s AND not l.blocked
AND m.state IN ('posted')
AND aa.account_type = %(account_type)s
GROUP BY l.partner_id, l.currency_id, l.date, l.date_maturity,
l.amount_currency, l.balance, l.move_id,
l.company_id, l.id
l.amount_currency, l.balance, l.move_id, l.company_id, l.id
""",
locals(),
),
@@ -358,6 +361,11 @@ class ReportStatementCommon(models.AbstractModel):
if isinstance(date_end, str):
date_end = datetime.strptime(date_end, DEFAULT_SERVER_DATE_FORMAT).date()
account_type = data["account_type"]
excluded_accounts_ids = data["excluded_accounts_ids"]
if excluded_accounts_ids:
self = self.with_context(
excluded_accounts_ids=excluded_accounts_ids,
)
aging_type = data["aging_type"]
is_activity = data.get("is_activity")
is_detailed = data.get("is_detailed")
@@ -583,6 +591,7 @@ class ReportStatementCommon(models.AbstractModel):
"company": self.env["res.company"].browse(company_id),
"Currencies": currencies,
"account_type": account_type,
"excluded_accounts_ids": excluded_accounts_ids,
"is_detailed": is_detailed,
"bucket_labels": bucket_labels,
"get_inv_addr": self._get_invoice_address,

View File

@@ -92,3 +92,59 @@ class TestOutstandingStatement(TransactionCase):
self.assertIn(
"bucket_labels", report, "There was an error while compiling the report."
)
def test_exclude_accounts(self):
"""Accounts can be excluded with a code selector."""
# Arrange
partners = self.partner1 | self.partner2
wizard = self.wiz.with_context(
active_ids=partners.ids,
).create({})
# Edit one invoice
# including a new account
# that will be the only one not excluded
partner_invoice = self.env["account.move"].search(
[
("partner_id", "in", partners.ids),
("state", "=", "posted"),
],
limit=1,
)
account = partner_invoice.line_ids.account_id.filtered(
lambda a: a.account_type == wizard.account_type
)
copy_account = account.copy()
partner_invoice.line_ids.filtered(
lambda line: line.account_id == account
).account_id = copy_account
partner_invoice.line_ids.flush_recordset()
wizard_accounts = self.env["account.account"].search(
[
("id", "!=", copy_account.id),
("account_type", "=", wizard.account_type),
],
)
wizard.excluded_accounts_selector = ", ".join(
[account.code for account in wizard_accounts]
)
# pre-condition
self.assertTrue(wizard.excluded_accounts_selector)
# Act
data = wizard._prepare_statement()
report = self.statement_model._get_report_values(partners.ids, data)
# Assert
# Only the new invoice is shown
invoice_partner = partner_invoice.partner_id
invoice_partner_data = report["data"][invoice_partner.id]["currencies"]
invoice_partner_move_lines = invoice_partner_data[
partner_invoice.currency_id.id
]["lines"]
self.assertEqual(len(invoice_partner_move_lines), 1)
self.assertEqual(invoice_partner_move_lines[0]["name"], partner_invoice.name)
other_partner = partners - invoice_partner
other_partner_data = report["data"].get(other_partner.id)
self.assertFalse(other_partner_data)

View File

@@ -4,6 +4,7 @@
from dateutil.relativedelta import relativedelta
from odoo import api, fields, models
from odoo.osv import expression
class StatementCommon(models.AbstractModel):
@@ -38,6 +39,44 @@ class StatementCommon(models.AbstractModel):
[("asset_receivable", "Receivable"), ("liability_payable", "Payable")],
default="asset_receivable",
)
excluded_accounts_selector = fields.Char(
string="Accounts to exclude",
help="Select account codes to be excluded "
"with a comma-separated list of expressions like 70%.",
)
@api.model
def _get_excluded_accounts_domain(self, selector):
"""Convert an account codes selector to a domain to search accounts.
The selector is a comma-separated list of expressions like 70%.
The algorithm is the same as
AccountingExpressionProcessor._account_codes_to_domain
of `mis_builder` module.
"""
if not selector:
selector = ""
domains = []
for account_code in selector.split(","):
account_code = account_code.strip()
if "%" in account_code:
domains.append(
[
("code", "=like", account_code),
]
)
else:
domains.append(
[
("code", "=", account_code),
]
)
return expression.OR(domains)
def _get_excluded_accounts(self):
self.ensure_one()
domain = self._get_excluded_accounts_domain(self.excluded_accounts_selector)
return self.env["account.account"].search(domain)
@api.onchange("aging_type")
def onchange_aging_type(self):
@@ -59,6 +98,7 @@ class StatementCommon(models.AbstractModel):
"account_type": self.account_type,
"aging_type": self.aging_type,
"filter_negative_balances": self.filter_negative_balances,
"excluded_accounts_ids": self._get_excluded_accounts().ids,
}
def button_export_html(self):

View File

@@ -63,6 +63,7 @@
/>
<label for="account_type" />
<field name="account_type" nolabel="1" widget="radio" />
<field name="excluded_accounts_selector" />
</group>
<group name="aging_report">
<field name="show_aging_buckets" />