Skip to content

Commit c444368

Browse files
committed
[ADD] account_reconcile_bg: background processing for large bank reconciliations
Implements background job processing for bank reconciliations when dealing with large payment batches to prevent timeouts and improve user experience. Main features: - Automatic background processing for large reconciliations (configurable threshold) - User notifications when background reconciliation completes - Seamless integration with existing bank reconciliation workflow - No UI blocking: users can continue working while large batches process Technical implementation: - Override bank.rec.widget._js_action_validate() to detect large reconciliations - Use base_bg to enqueue reconciliation jobs - Add reconciliation_in_background flag to account.bank.statement.line - Configurable via system parameter: account_reconcile_bg.lines_threshold Includes comprehensive testing guide and unit tests.
1 parent c7afeb3 commit c444368

11 files changed

Lines changed: 488 additions & 0 deletions

account_reconcile_bg/README.rst

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
Account Reconcile Background
2+
============================
3+
4+
This module enables background processing for bank reconciliation operations when dealing with large payment batches, preventing timeouts and improving user experience.
5+
6+
**Table of contents**
7+
8+
.. contents::
9+
:local:
10+
11+
Overview
12+
========
13+
14+
When reconciling large payment batches (e.g., multiple payments included in a single batch) with a bank statement line, the operation can take a long time and may cause timeouts. This module solves this problem by automatically processing large reconciliations in the background, allowing users to continue working while the reconciliation completes.
15+
16+
Features
17+
========
18+
19+
* **Automatic Background Processing**: Large reconciliations are automatically sent to background processing
20+
* **Configurable Threshold**: System parameter to control when background processing kicks in (default: 50 lines)
21+
* **User Notifications**: Users receive notifications when background reconciliation completes
22+
* **No UI Blocking**: Users can continue reconciling other transactions while large batches process
23+
* **Seamless Integration**: Works transparently with existing bank reconciliation workflow
24+
25+
How It Works
26+
============
27+
28+
The module monitors the number of lines being reconciled in the bank reconciliation widget:
29+
30+
1. When validating a reconciliation, it counts the number of source lines
31+
2. If the count is **below the threshold** (default 50), the reconciliation proceeds normally (synchronous)
32+
3. If the count is **above the threshold**, the reconciliation is enqueued as a background job
33+
4. The user receives an immediate success notification and can continue working
34+
5. When the background job completes, the user is notified via internal message
35+
36+
Configuration
37+
=============
38+
39+
The threshold for background processing can be configured via system parameters:
40+
41+
* Navigate to **Settings > Technical > Parameters > System Parameters**
42+
* Find or create the parameter ``account_reconcile_bg.lines_threshold``
43+
* Default value: ``50``
44+
* Set to a higher value to process larger reconciliations synchronously
45+
* Set to a lower value to send more reconciliations to background
46+
47+
Technical Details
48+
=================
49+
50+
Dependencies
51+
------------
52+
53+
* ``account_accountant``: Odoo Enterprise accounting module with bank reconciliation
54+
* ``base_bg``: Background job processing system
55+
56+
Model Inheritance
57+
-----------------
58+
59+
The module inherits from ``bank.rec.widget`` and overrides:
60+
61+
* ``_js_action_validate()``: Detects large reconciliations and routes to background
62+
* ``_validate_in_background()``: Enqueues the job using base_bg
63+
* ``_do_validate()``: Executes the actual validation in background
64+
65+
Credits
66+
=======
67+
68+
Authors
69+
-------
70+
71+
* ADHOC SA
72+
73+
Contributors
74+
------------
75+
76+
* ADHOC SA
77+
78+
Maintainers
79+
-----------
80+
81+
This module is maintained by ADHOC SA.

account_reconcile_bg/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
2+
from . import models
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
##############################################################################
2+
#
3+
# Copyright (C) 2026 ADHOC SA (http://www.adhoc.com.ar)
4+
# All Rights Reserved.
5+
#
6+
# This program is free software: you can redistribute it and/or modify
7+
# it under the terms of the GNU Affero General Public License as
8+
# published by the Free Software Foundation, either version 3 of the
9+
# License, or (at your option) any later version.
10+
#
11+
# This program is distributed in the hope that it will be useful,
12+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
# GNU Affero General Public License for more details.
15+
#
16+
# You should have received a copy of the GNU Affero General Public License
17+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
#
19+
##############################################################################
20+
{
21+
"name": "Account Reconcile Background",
22+
"version": "18.0.1.0.0",
23+
"category": "Accounting",
24+
"author": "ADHOC SA",
25+
"website": "https://www.adhoc.com.ar",
26+
"license": "AGPL-3",
27+
"summary": "Process bank reconciliation in background for large payment batches",
28+
"depends": [
29+
"account_accountant",
30+
"base_bg",
31+
],
32+
"data": [
33+
"data/ir_config_parameter_data.xml",
34+
],
35+
"installable": True,
36+
"auto_install": False,
37+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<odoo>
3+
4+
<!-- Parámetro de configuración para el umbral de líneas -->
5+
<record id="config_lines_threshold" model="ir.config_parameter">
6+
<field name="key">account_reconcile_bg.lines_threshold</field>
7+
<field name="value">100</field>
8+
</record>
9+
10+
</odoo>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
2+
from . import bank_rec_widget
3+
from . import account_bank_statement_line
4+
from . import account_move_line
5+
from . import bg_job
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
##############################################################################
2+
# For copyright and license notices, see __manifest__.py file in module root
3+
# directory
4+
##############################################################################
5+
import logging
6+
7+
from markupsafe import Markup
8+
from odoo import _, api, fields, models
9+
from odoo.exceptions import UserError
10+
11+
12+
class AccountBankStatementLine(models.Model):
13+
_inherit = "account.bank.statement.line"
14+
15+
reconciliation_in_background = fields.Boolean(
16+
string="Reconciliation in Background",
17+
default=False,
18+
readonly=True,
19+
help="Indicates that this line is being reconciled in background",
20+
)
21+
22+
def _bg_validate_reconciliation(self, selected_aml_ids=None):
23+
"""
24+
Método ejecutado en background para validar la conciliación.
25+
Se llama desde el job de base_bg.
26+
27+
:param selected_aml_ids: IDs de las líneas seleccionadas por el usuario
28+
"""
29+
self.ensure_one()
30+
_logger = logging.getLogger(__name__)
31+
32+
# Preparar datos para mensaje
33+
base_url = self.env["ir.config_parameter"].sudo().get_param("web.base.url")
34+
st_line_url = f"{base_url}/odoo/account.bank.statement.line/{self.id}"
35+
st_line_name = self.name or f"Line {self.id}"
36+
37+
try:
38+
# Crear el widget de conciliación
39+
wizard = self.env["bank.rec.widget"].with_context(default_st_line_id=self.id).new({})
40+
41+
_logger.info(f"[BG] Wizard created for st_line {self.id}")
42+
43+
# Agregar las líneas al widget correctamente usando el método interno
44+
if selected_aml_ids:
45+
amls = self.env["account.move.line"].browse(selected_aml_ids)
46+
wizard._action_add_new_amls(amls, allow_partial=False)
47+
48+
# Ejecutar la validación con el context manager
49+
with wizard._action_validate_method():
50+
wizard._action_validate()
51+
52+
# Retornar mensaje de éxito
53+
return Markup(
54+
_("Bank reconciliation completed successfully:<br><a href='%s' target='_blank'>%s</a>")
55+
% (st_line_url, st_line_name)
56+
)
57+
except Exception as e:
58+
return Markup(
59+
_("Bank reconciliation failed:<br><a href='%s' target='_blank'>%s</a><br><br>Error: %s")
60+
% (st_line_url, st_line_name, str(e))
61+
)
62+
finally:
63+
self.write({"reconciliation_in_background": False})
64+
65+
@api.constrains(
66+
"balance",
67+
"amount_currency",
68+
"currency_id",
69+
)
70+
def _check_reconciliation_in_background(self):
71+
"""Valida que no se modifiquen líneas en proceso de conciliación background."""
72+
if self.env.context.get("bg_job"):
73+
return
74+
for line in self:
75+
if line.reconciliation_in_background:
76+
raise UserError(
77+
_(
78+
"Cannot modify payment lines that are being reconciled in background. "
79+
"Please wait until the reconciliation process is complete.\n"
80+
"Journal Entry (id): %(entry)s (%(id)s)",
81+
entry=line.move_id.name,
82+
id=line.move_id.id,
83+
)
84+
)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
##############################################################################
2+
# For copyright and license notices, see __manifest__.py file in module root
3+
# directory
4+
##############################################################################
5+
from odoo import fields, models
6+
7+
8+
class AccountMoveLine(models.Model):
9+
_inherit = "account.move.line"
10+
11+
reconciliation_in_background = fields.Boolean(
12+
string="Reconciliation in Background",
13+
default=False,
14+
readonly=True,
15+
help="Indicates that this line is being reconciled in background",
16+
)
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
##############################################################################
2+
# For copyright and license notices, see __manifest__.py file in module root
3+
# directory
4+
##############################################################################
5+
import logging
6+
7+
from odoo import _, models
8+
from odoo.exceptions import UserError
9+
10+
11+
class BankRecWidget(models.Model):
12+
_inherit = "bank.rec.widget"
13+
14+
def _js_action_validate(self):
15+
"""
16+
Override para procesar conciliaciones grandes en background.
17+
Si hay muchas líneas (> threshold), usa base_bg para procesarlo en 2do plano.
18+
"""
19+
self.ensure_one()
20+
21+
# Verificar si ya está procesando en background
22+
if self.st_line_id.reconciliation_in_background:
23+
raise UserError(
24+
_("This reconciliation is already being processed in background. Please wait until it finishes.")
25+
)
26+
27+
# Invalidar caché y refrescar para obtener el estado actual desde la BD
28+
self.selected_aml_ids.invalidate_recordset(fnames=["reconciliation_in_background"])
29+
30+
# Verificar si alguna de las líneas seleccionadas ya está en background (en cualquier extracto)
31+
lines_in_bg = self.selected_aml_ids.filtered("reconciliation_in_background")
32+
if lines_in_bg:
33+
raise UserError(
34+
_(
35+
"Some of the selected payment lines (%s) are already being reconciled in background on another statement. "
36+
"Please wait until they finish or select different lines."
37+
)
38+
% len(lines_in_bg)
39+
)
40+
41+
# Obtener el umbral de líneas desde parámetros del sistema (default: 50)
42+
threshold = int(self.env["ir.config_parameter"].sudo().get_param("account_reconcile_bg.lines_threshold", "50"))
43+
44+
# Contar las líneas seleccionadas para conciliar
45+
lines_count = len(self.selected_aml_ids)
46+
47+
# DEBUG: Log para verificar
48+
49+
# Si hay pocas líneas, ejecutar el proceso normal de manera sincrónica
50+
if lines_count < threshold:
51+
return super()._js_action_validate()
52+
53+
# Si hay muchas líneas, procesar en background
54+
return self._validate_in_background()
55+
56+
def _validate_in_background(self):
57+
"""
58+
Encola la validación de conciliación en background usando base_bg.
59+
Nota: Como bank.rec.widget no se persiste, encolamos usando st_line_id.
60+
"""
61+
self.ensure_one()
62+
63+
_logger = logging.getLogger(__name__)
64+
65+
# Marcar la línea de extracto y las líneas de pago como procesando en background
66+
self.st_line_id.write({"reconciliation_in_background": True})
67+
self.selected_aml_ids.write({"reconciliation_in_background": True})
68+
69+
# Flush para asegurar que los cambios se escriben inmediatamente en la BD
70+
# Esto previene condiciones de carrera donde otro usuario podría conciliar las mismas líneas
71+
self.env.flush_all()
72+
73+
# Capturar los IDs antes de encolar
74+
selected_ids = self.selected_aml_ids.ids
75+
_logger.info(f"[account_reconcile_bg] Capturing selected_aml_ids: {selected_ids}")
76+
77+
try:
78+
# Encolar el job usando la línea de extracto (modelo persistente)
79+
_action, _jobs = self.env["base.bg"].bg_enqueue_records(
80+
self.st_line_id,
81+
"_bg_validate_reconciliation",
82+
threshold=1, # Un job por línea
83+
name=_("Bank Reconciliation: %s") % self.st_line_id.name,
84+
priority=5, # Alta prioridad
85+
selected_aml_ids=selected_ids, # Pasar solo los IDs (lista de enteros)
86+
)
87+
_logger.info("[account_reconcile_bg] Job enqueued successfully")
88+
except Exception:
89+
# Si falla al encolar, limpiar los flags
90+
self.st_line_id.write({"reconciliation_in_background": False})
91+
self.selected_aml_ids.write({"reconciliation_in_background": False})
92+
raise
93+
94+
# Enviar notificación al usuario usando el bus
95+
self.env["bus.bus"]._sendone(
96+
self.env.user.partner_id,
97+
"simple_notification",
98+
{
99+
"type": "success",
100+
"message": _(
101+
"This reconciliation is being processed in background. You will be notified when it's done."
102+
),
103+
},
104+
)
105+
106+
# Configurar el comando para el widget
107+
self.return_todo_command = {"done": True}
108+
109+
# Retornar vacío - el widget usa return_todo_command
110+
return
111+
112+
def _do_validate(self):
113+
"""
114+
Método que ejecuta la validación real en background.
115+
Se llama desde el job de base_bg.
116+
"""
117+
self.ensure_one()
118+
# Ejecutar la validación usando el método context manager
119+
with self._action_validate_method():
120+
self._action_validate()

0 commit comments

Comments
 (0)