mirror of
https://github.com/OCA/account-financial-tools.git
synced 2025-02-02 12:47:26 +02:00
[FIX] account_move_line_cumulated_balance: Absolute SQL strategy
As other partial ones doesn't fully work. The performance is OK enough for a big amount of records (~2 s for 25k records on the same account), so we accept the trade-off for having correct data.
This commit is contained in:
@@ -58,8 +58,13 @@ Usage
|
||||
Known issues / Roadmap
|
||||
======================
|
||||
|
||||
* For v14, there's only need to migrate the field amount_currency_balance, as
|
||||
the other one already exists in core.
|
||||
* For v14, there's a similar feature, but it doesn't cumulate in an absolute way like
|
||||
this one, and there's no cumulated in amount currency, so this module may be fully
|
||||
preserved.
|
||||
* There's no support for filtering at the same time by several entries state (posted,
|
||||
not posted, cancel, etc).
|
||||
* In the partner ledger, removing the group by partner won't make the cumulated balances
|
||||
to be considered globally by account.
|
||||
|
||||
Bug Tracker
|
||||
===========
|
||||
|
||||
@@ -28,46 +28,50 @@ class AccountMoveLine(models.Model):
|
||||
|
||||
@api.model
|
||||
def search_read(self, domain=None, fields=None, offset=0, limit=None, order=None):
|
||||
def to_tuple(t):
|
||||
return tuple(map(to_tuple, t)) if isinstance(t, (list, tuple)) else t
|
||||
|
||||
# Add the domain and order by in order to compute the cumulated
|
||||
# balance in _compute_cumulated_balance
|
||||
order = (order or self._order) + ", id desc"
|
||||
# Add the significant domain in order to compute the cumulated balance in
|
||||
# _compute_cumulated_balance.
|
||||
cumulated_domain = []
|
||||
for term in domain:
|
||||
if isinstance(term, (tuple, list)) and term[0] == "move_id.state":
|
||||
# TODO: Allow multiple state conditions joined by OR
|
||||
cumulated_domain.append(("parent_state", term[1], term[2]))
|
||||
elif term[0] == "full_reconcile_id":
|
||||
cumulated_domain.append(tuple(term))
|
||||
return super(
|
||||
AccountMoveLine, self.with_context(order_cumulated_balance=order,),
|
||||
AccountMoveLine,
|
||||
self.with_context(domain_cumulated_balance=cumulated_domain),
|
||||
).search_read(domain, fields, offset, limit, order)
|
||||
|
||||
@api.depends_context("order_cumulated_balance")
|
||||
@api.depends_context("domain_cumulated_balance", "partner_ledger")
|
||||
def _compute_cumulated_balance(self):
|
||||
self.cumulated_balance = 0
|
||||
self.cumulated_balance_currency = 0
|
||||
order_cumulated_balance = (
|
||||
self.env.context.get("order_cumulated_balance", self._order) + ", id"
|
||||
)
|
||||
order_string = ", ".join(
|
||||
self._generate_order_by_inner(
|
||||
self._table, order_cumulated_balance, "", reverse_direction=False,
|
||||
)
|
||||
)
|
||||
query = sql.SQL(
|
||||
"""SELECT account_move_line.id,
|
||||
SUM(account_move_line.balance) OVER (
|
||||
ORDER BY {order_by_clause}
|
||||
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
|
||||
),
|
||||
SUM(account_move_line.amount_currency) OVER (
|
||||
ORDER BY {order_by_clause}
|
||||
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
|
||||
)
|
||||
FROM account_move_line
|
||||
LEFT JOIN account_move on account_move_line.move_id = account_move.id
|
||||
WHERE
|
||||
account_move.state = 'posted'
|
||||
"""
|
||||
).format(order_by_clause=sql.SQL(order_string),)
|
||||
self.env.cr.execute(query)
|
||||
result = {r[0]: (r[1], r[2]) for r in self.env.cr.fetchall()}
|
||||
query = self._where_calc(self.env.context.get("domain_cumulated_balance") or [])
|
||||
_f, where_clause, where_clause_params = query.get_sql()
|
||||
for record in self:
|
||||
record.cumulated_balance = result[record.id][0]
|
||||
record.cumulated_balance_currency = result[record.id][1]
|
||||
query_args = where_clause_params + [
|
||||
record.account_id.id,
|
||||
record.company_id.id,
|
||||
record.date,
|
||||
record.date,
|
||||
record.id,
|
||||
]
|
||||
# WHERE clause last line is set according order in view where this is used
|
||||
query = sql.SQL(
|
||||
"""
|
||||
SELECT SUM(balance), SUM(amount_currency)
|
||||
FROM account_move_line
|
||||
WHERE {}
|
||||
AND account_id = %s
|
||||
AND company_id = %s
|
||||
AND (date < %s OR (date=%s AND id <= %s))
|
||||
"""
|
||||
).format(sql.SQL(where_clause or "TRUE"))
|
||||
if self.env.context.get("partner_ledger"):
|
||||
# If showing partner ledger group by partner by default
|
||||
query_args.append(record.partner_id.id)
|
||||
query += sql.SQL("AND partner_id = %s")
|
||||
self.env.cr.execute(query, tuple(query_args))
|
||||
result = self.env.cr.fetchone()
|
||||
record.cumulated_balance = result[0]
|
||||
record.cumulated_balance_currency = result[1]
|
||||
|
||||
@@ -1,2 +1,7 @@
|
||||
* For v14, there's only need to migrate the field amount_currency_balance, as
|
||||
the other one already exists in core.
|
||||
* For v14, there's a similar feature, but it doesn't cumulate in an absolute way like
|
||||
this one, and there's no cumulated in amount currency, so this module may be fully
|
||||
preserved.
|
||||
* There's no support for filtering at the same time by several entries state (posted,
|
||||
not posted, cancel, etc).
|
||||
* In the partner ledger, removing the group by partner won't make the cumulated balances
|
||||
to be considered globally by account.
|
||||
|
||||
@@ -1,31 +1,20 @@
|
||||
# Copyright 2020 Tecnativa - Víctor Martínez
|
||||
# License AGPL-3 - See https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
from odoo.tests import Form
|
||||
from odoo.tests import Form, tagged
|
||||
from odoo.tests.common import SavepointCase
|
||||
|
||||
|
||||
@tagged("post_install", "-at_install")
|
||||
class TestAccount(SavepointCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.account_account_type_model = cls.env["account.account.type"]
|
||||
cls.account_account_model = cls.env["account.account"]
|
||||
cls.account_type_receivable = cls.account_account_type_model.create(
|
||||
{"name": "Test Receivable", "type": "receivable", "internal_group": "asset"}
|
||||
)
|
||||
cls.account_type_regular = cls.account_account_type_model.create(
|
||||
{"name": "Test Regular", "type": "other", "internal_group": "income"}
|
||||
)
|
||||
|
||||
cls.account_receivable = cls.account_account_model.create(
|
||||
{
|
||||
"name": "Test Receivable",
|
||||
"code": "TEST_AR",
|
||||
"user_type_id": cls.account_type_receivable.id,
|
||||
"reconcile": True,
|
||||
}
|
||||
)
|
||||
cls.account_income = cls.account_account_model.create(
|
||||
{
|
||||
"name": "Test Income",
|
||||
@@ -34,21 +23,7 @@ class TestAccount(SavepointCase):
|
||||
"reconcile": False,
|
||||
}
|
||||
)
|
||||
cls.partner = cls.env["res.partner"].create(
|
||||
{
|
||||
"name": "Test customer",
|
||||
"customer_rank": 1,
|
||||
"property_account_receivable_id": cls.account_receivable.id,
|
||||
}
|
||||
)
|
||||
cls.journal = cls.env["account.journal"].create(
|
||||
{
|
||||
"name": "Test journal",
|
||||
"type": "sale",
|
||||
"code": "test-sale-jorunal",
|
||||
"company_id": cls.env.company.id,
|
||||
}
|
||||
)
|
||||
cls.partner = cls.env["res.partner"].create({"name": "Test customer"})
|
||||
cls.product = cls.env["product.product"].create(
|
||||
{"name": "Test product", "type": "service"}
|
||||
)
|
||||
@@ -58,27 +33,36 @@ class TestAccount(SavepointCase):
|
||||
)
|
||||
)
|
||||
invoice.partner_id = cls.partner
|
||||
invoice.journal_id = cls.journal
|
||||
with invoice.invoice_line_ids.new() as line_form:
|
||||
line_form.name = cls.product.name
|
||||
line_form.product_id = cls.product
|
||||
line_form.quantity = 1.0
|
||||
line_form.price_unit = 10
|
||||
line_form.account_id = cls.account_income
|
||||
with invoice.invoice_line_ids.new() as line_form:
|
||||
line_form.name = cls.product.name
|
||||
line_form.product_id = cls.product
|
||||
line_form.quantity = 2.0
|
||||
line_form.price_unit = 10
|
||||
line_form.account_id = cls.account_income
|
||||
invoice = invoice.save()
|
||||
invoice.action_post()
|
||||
cls.invoice = invoice
|
||||
|
||||
def test_remove_invoice_error(self):
|
||||
# Delete invoice while name isn't /
|
||||
def test_basic_check(self):
|
||||
lines = (
|
||||
self.env["account.move.line"]
|
||||
.with_context(order_cumulated_balance="date desc, id desc")
|
||||
.with_context(domain_cumulated_balance=[("parent_state", "=", "posted")])
|
||||
.search(
|
||||
[("move_id.state", "=", "posted"), ("move_id", "=", self.invoice.id)]
|
||||
[
|
||||
("account_id", "=", self.account_income.id),
|
||||
("move_id", "=", self.invoice.id),
|
||||
],
|
||||
order="date asc, id asc",
|
||||
)
|
||||
)
|
||||
self.assertAlmostEqual(lines[0].cumulated_balance, 0)
|
||||
self.assertAlmostEqual(lines[0].cumulated_balance, -10)
|
||||
# TODO: Test other currency balances
|
||||
self.assertAlmostEqual(lines[0].cumulated_balance_currency, 0)
|
||||
self.assertAlmostEqual(lines[1].cumulated_balance, 10)
|
||||
self.assertAlmostEqual(lines[1].cumulated_balance, -30)
|
||||
self.assertAlmostEqual(lines[1].cumulated_balance_currency, 0)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<field name="inherit_id" ref="account.view_move_line_tree_grouped_general" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="/tree" position="attributes">
|
||||
<attribute name="default_order">date asc</attribute>
|
||||
<attribute name="default_order">date asc, id asc</attribute>
|
||||
</xpath>
|
||||
|
||||
<field name="amount_currency" position="before">
|
||||
@@ -26,9 +26,9 @@
|
||||
<field name="inherit_id" ref="account.view_move_line_tree_grouped_partner" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="/tree" position="attributes">
|
||||
<attribute name="default_order">date asc</attribute>
|
||||
<attribute name="default_order">date asc, id asc</attribute>
|
||||
</xpath>
|
||||
<field name="credit" position="after">
|
||||
<field name="amount_currency" position="before">
|
||||
<field name="cumulated_balance" optional="show" />
|
||||
</field>
|
||||
<field name="amount_currency" position="after">
|
||||
@@ -40,4 +40,13 @@
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
<!-- Inject a context key for putting cumulated balance grouped by partner -->
|
||||
<record
|
||||
id="account.action_account_moves_ledger_partner"
|
||||
model="ir.actions.act_window"
|
||||
>
|
||||
<field
|
||||
name="context"
|
||||
>{'partner_ledger': 1, 'journal_type':'general', 'search_default_group_by_partner': 1, 'search_default_posted':1, 'search_default_payable':1, 'search_default_receivable':1, 'search_default_unreconciled':1}</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
||||
Reference in New Issue
Block a user