diff --git a/addons/account/data/account_data.xml b/addons/account/data/account_data.xml
index 628649f58e8..4ca719fa04c 100644
--- a/addons/account/data/account_data.xml
+++ b/addons/account/data/account_data.xml
@@ -97,18 +97,6 @@
Command.create({'value': 'percent', 'value_amount': 100.0, 'nb_days': 30})]"/>
-
-
- Account reconcile sequence
- account.reconcile
- A
-
-
-
-
-
Validated
diff --git a/addons/account/models/account_full_reconcile.py b/addons/account/models/account_full_reconcile.py
index d701816f756..a27c46839ec 100644
--- a/addons/account/models/account_full_reconcile.py
+++ b/addons/account/models/account_full_reconcile.py
@@ -6,7 +6,6 @@ class AccountFullReconcile(models.Model):
_name = "account.full.reconcile"
_description = "Full Reconcile"
- name = fields.Char(string='Number', required=True, copy=False, default=lambda self: self.env['ir.sequence'].next_by_code('account.reconcile'))
partial_reconcile_ids = fields.One2many('account.partial.reconcile', 'full_reconcile_id', string='Reconciliation Parts')
reconciled_line_ids = fields.One2many('account.move.line', 'full_reconcile_id', string='Matched Journal Items')
exchange_move_id = fields.Many2one('account.move', index="btree_not_null")
@@ -35,3 +34,10 @@ class AccountFullReconcile(models.Model):
moves_to_reverse._reverse_moves(default_values_list, cancel=True)
return res
+
+ @api.model_create_multi
+ def create(self, vals_list):
+ fulls = super().create(vals_list)
+ for full in fulls:
+ full.reconciled_line_ids.matching_number = str(full.id)
+ return fulls
diff --git a/addons/account/models/account_move_line.py b/addons/account/models/account_move_line.py
index 268d15efd63..7d3640789d2 100644
--- a/addons/account/models/account_move_line.py
+++ b/addons/account/models/account_move_line.py
@@ -256,7 +256,7 @@ class AccountMoveLine(models.Model):
)
matching_number = fields.Char(
string="Matching #",
- compute='_compute_matching_number', store=True,
+ readonly=True,
help="Matching number for this line, 'P' if it is only partially reconcile, or the name of "
"the full reconcile if it exists.",
)
@@ -666,16 +666,6 @@ class AccountMoveLine(models.Model):
if line.currency_id == line.company_id.currency_id:
line.amount_currency = line.balance
- @api.depends('full_reconcile_id.name', 'matched_debit_ids', 'matched_credit_ids')
- def _compute_matching_number(self):
- for record in self:
- if record.full_reconcile_id:
- record.matching_number = record.full_reconcile_id.name
- elif record.matched_debit_ids or record.matched_credit_ids:
- record.matching_number = 'P'
- else:
- record.matching_number = None
-
@api.depends_context('order_cumulated_balance', 'domain_cumulated_balance')
def _compute_cumulated_balance(self):
if not self.env.context.get('order_cumulated_balance'):
@@ -2948,13 +2938,22 @@ class AccountMoveLine(models.Model):
def _all_reconciled_lines(self):
reconciliation_lines = self.filtered(lambda x: x.account_id.reconcile or x.account_id.account_type in ('asset_cash', 'liability_credit_card'))
- current_lines = reconciliation_lines
- current_partials = self.env['account.partial.reconcile']
- while current_lines:
- current_partials = (current_lines.matched_debit_ids + current_lines.matched_credit_ids) - current_partials
- current_lines = (current_partials.debit_move_id + current_partials.credit_move_id) - current_lines
- reconciliation_lines += current_lines
- return reconciliation_lines
+ self.env['account.partial.reconcile'].flush_model()
+ self.env.cr.execute("""
+ WITH RECURSIVE partials (current_id) AS (
+ SELECT line.id
+ FROM account_move_line line
+ WHERE id = ANY(%s)
+
+ UNION
+
+ SELECT CASE WHEN partial.debit_move_id = p.current_id THEN partial.credit_move_id ELSE partial.debit_move_id END
+ FROM partials p
+ JOIN account_partial_reconcile partial ON partial.debit_move_id = p.current_id OR partial.credit_move_id = p.current_id
+ )
+ SELECT current_id FROM partials;
+ """, [reconciliation_lines.ids])
+ return self.browse(r[0] for r in self.env.cr.fetchall())
def _get_attachment_domains(self):
self.ensure_one()
diff --git a/addons/account/models/account_partial_reconcile.py b/addons/account/models/account_partial_reconcile.py
index 26098971bff..d86198049e7 100644
--- a/addons/account/models/account_partial_reconcile.py
+++ b/addons/account/models/account_partial_reconcile.py
@@ -8,7 +8,6 @@ from datetime import date
class AccountPartialReconcile(models.Model):
_name = "account.partial.reconcile"
_description = "Partial Reconcile"
- _rec_name = "id"
# ==== Reconciliation fields ====
debit_move_id = fields.Many2one(
@@ -106,6 +105,7 @@ class AccountPartialReconcile(models.Model):
# Retrieve the matching number to unlink.
full_to_unlink = self.full_reconcile_id
+ all_reconciled = self.debit_move_id + self.credit_move_id
# Retrieve the CABA entries to reverse.
moves_to_reverse = self.env['account.move'].search([('tax_cash_basis_rec_id', 'in', self.ids)])
@@ -126,8 +126,57 @@ class AccountPartialReconcile(models.Model):
} for move in moves_to_reverse]
moves_to_reverse._reverse_moves(default_values_list, cancel=True)
+ self._update_matching_number(all_reconciled)
return res
+ @api.model_create_multi
+ def create(self, vals_list):
+ partials = super().create(vals_list)
+ self._update_matching_number(partials.debit_move_id + partials.credit_move_id)
+ return partials
+
+ @api.model
+ def _update_matching_number(self, amls):
+ amls = amls._all_reconciled_lines()
+ self.env['account.partial.reconcile'].flush_model()
+ amls.flush_recordset(['full_reconcile_id'])
+ self.env.cr.execute("""
+ WITH RECURSIVE partials (line_id, current_id) AS (
+ SELECT id, id
+ FROM account_move_line
+ WHERE id = ANY(%s)
+ AND full_reconcile_id IS NULL
+
+ UNION
+
+ SELECT p.line_id,
+ CASE WHEN partial.debit_move_id = p.current_id THEN partial.credit_move_id
+ ELSE partial.debit_move_id
+ END
+ FROM partials p
+ JOIN account_partial_reconcile partial ON p.current_id = partial.debit_move_id
+ OR p.current_id = partial.credit_move_id
+ )
+ SELECT line_id, 'P' || MIN(partial.id) AS matching_number
+ FROM partials
+ JOIN account_partial_reconcile partial ON current_id = partial.debit_move_id
+ OR current_id = partial.credit_move_id
+ GROUP BY line_id
+ """, [
+ amls.ids,
+ ])
+
+ line_matching_number = dict(self.env.cr.fetchall())
+
+ for line in amls:
+ if line.full_reconcile_id:
+ line.matching_number = str(line.full_reconcile_id.id)
+ elif line.matched_debit_ids or line.matched_credit_ids:
+ line.matching_number = line_matching_number[line.id]
+ else:
+ line.matching_number = False
+
+
# -------------------------------------------------------------------------
# RECONCILIATION METHODS
# -------------------------------------------------------------------------
diff --git a/addons/account/tests/test_account_move_reconcile.py b/addons/account/tests/test_account_move_reconcile.py
index f8a11433493..6cf0d32a25d 100644
--- a/addons/account/tests/test_account_move_reconcile.py
+++ b/addons/account/tests/test_account_move_reconcile.py
@@ -4000,6 +4000,104 @@ class TestAccountMoveReconcile(AccountTestInvoicingCommon):
{'debit': 0.0, 'credit': 0.01, 'tax_ids': [], 'tax_tag_ids': self.tax_tags[5].ids, 'account_id': self.tax_account_2.id},
])
+ def test_matching_number_full_reconcile(self):
+ currency = self.env.company.currency_id
+ line_a = self.create_line_for_reconciliation(1000, 1000, currency, '2016-01-01')
+ line_b = self.create_line_for_reconciliation(-1000, -1000, currency, '2016-01-01')
+ (line_a + line_b).reconcile()
+ self.assertFullReconcile(line_a.full_reconcile_id, (line_a + line_b))
+ self.assertEqual(line_a.matching_number, str(line_a.full_reconcile_id.id))
+ self.assertEqual(line_a.matching_number, line_b.matching_number)
+
+ def test_matching_number_partial_single_reconcile(self):
+ currency = self.env.company.currency_id
+ line_a = self.create_line_for_reconciliation(1000, 1000, currency, '2016-01-01')
+ line_b = self.create_line_for_reconciliation(-500, -500, currency, '2016-01-01')
+ (line_a + line_b).reconcile()
+ self.assertEqual(line_a.matching_number, f'P{line_a.matched_credit_ids.id}')
+ self.assertEqual(line_a.matching_number, line_b.matching_number)
+
+ def test_matching_number_partial_multi_reconcile(self):
+ currency = self.env.company.currency_id
+ line_a = self.create_line_for_reconciliation(1000, 1000, currency, '2016-01-01')
+ line_b = self.create_line_for_reconciliation(-500, -500, currency, '2016-01-01')
+ line_c = self.create_line_for_reconciliation(-1000, -1000, currency, '2016-01-01')
+ line_d = self.create_line_for_reconciliation(1000, 1000, currency, '2016-01-01')
+ (line_a + line_b).reconcile()
+ (line_a + line_c).reconcile()
+ (line_c + line_d).reconcile()
+ self.assertEqual(line_a.matching_number, f'P{line_a.matched_credit_ids.ids[0]}')
+ self.assertEqual(line_b.matching_number, line_a.matching_number)
+ self.assertEqual(line_c.matching_number, line_a.matching_number)
+ self.assertEqual(line_d.matching_number, line_a.matching_number)
+
+ line_b.remove_move_reconcile()
+ self.assertEqual(line_a.matching_number, f'P{line_a.matched_credit_ids.ids[0]}')
+ self.assertEqual(line_b.matching_number, False)
+ self.assertEqual(line_c.matching_number, f'P{line_c.matched_debit_ids.ids[0]}')
+ self.assertEqual(line_d.matching_number, line_c.matching_number)
+
+ (line_a + line_b).reconcile() # everything should be matched again
+ self.assertEqual(line_a.matching_number, line_c.matching_number)
+ self.assertEqual(line_b.matching_number, line_c.matching_number)
+ self.assertEqual(line_c.matching_number, f'P{line_c.matched_debit_ids.ids[0]}')
+ self.assertEqual(line_d.matching_number, line_c.matching_number)
+
+ def test_matching_number_partial_multi_separate_reconcile(self):
+ currency = self.env.company.currency_id
+ line_a = self.create_line_for_reconciliation(1000, 1000, currency, '2016-01-01')
+ line_b = self.create_line_for_reconciliation(-500, -500, currency, '2016-01-01')
+ (line_a + line_b).reconcile()
+ self.assertEqual(line_a.matching_number, f'P{line_a.matched_credit_ids.id}')
+ self.assertEqual(line_a.matching_number, line_b.matching_number)
+
+ line_c = self.create_line_for_reconciliation(-300, -300, currency, '2016-01-01')
+ (line_a + line_c).reconcile()
+ self.assertEqual(line_a.matching_number, f'P{line_a.matched_credit_ids.ids[0]}')
+ self.assertEqual(line_a.matching_number, line_b.matching_number)
+ self.assertEqual(line_a.matching_number, line_c.matching_number)
+
+ def test_matching_number_unreconcile_single(self):
+ currency = self.env.company.currency_id
+ full_line_a = self.create_line_for_reconciliation(200, 200, currency, '2016-01-01')
+ full_line_b = self.create_line_for_reconciliation(-200, -200, currency, '2016-01-01')
+ partial_line_a = self.create_line_for_reconciliation(1000, 1000, currency, '2016-01-01')
+ partial_line_b = self.create_line_for_reconciliation(-500, -500, currency, '2016-01-01')
+ (full_line_a + full_line_b).reconcile()
+ (partial_line_a + partial_line_b).reconcile()
+ (full_line_a + full_line_b + partial_line_a + partial_line_b).remove_move_reconcile()
+ self.assertFalse(full_line_a.matching_number)
+ self.assertFalse(full_line_b.matching_number)
+ self.assertFalse(partial_line_a.matching_number)
+ self.assertFalse(partial_line_b.matching_number)
+
+ def test_matching_number_unreconcile_multi(self):
+ currency = self.env.company.currency_id
+ line_a = self.create_line_for_reconciliation(-500, -500, currency, '2016-01-01')
+ line_b = self.create_line_for_reconciliation(1000, 1000, currency, '2016-01-01')
+ line_c = self.create_line_for_reconciliation(-1000, -1000, currency, '2016-01-01')
+ line_d = self.create_line_for_reconciliation(300, 300, currency, '2016-01-01')
+ (line_a + line_b).reconcile()
+ (line_b + line_c).reconcile()
+ (line_c + line_d).reconcile()
+
+ previous_matching_number = line_a.matching_number
+ line_a.remove_move_reconcile()
+ self.assertFalse(line_a.matching_number)
+ self.assertNotEqual(previous_matching_number, line_b.matching_number)
+ self.assertEqual(line_b.matching_number, line_c.matching_number)
+ self.assertEqual(line_b.matching_number, line_d.matching_number)
+
+ previous_matching_number = line_b.matching_number
+ line_b.remove_move_reconcile()
+ self.assertFalse(line_b.matching_number)
+ self.assertNotEqual(previous_matching_number, line_c.matching_number)
+ self.assertEqual(line_c.matching_number, line_d.matching_number)
+
+ line_c.remove_move_reconcile()
+ self.assertFalse(line_c.matching_number)
+ self.assertFalse(line_d.matching_number)
+
def test_caba_mix_reconciliation(self):
""" Test the reconciliation of tax lines (when using a reconcilable tax account)
for cases mixing taxes exigible on payment and on invoices.
diff --git a/addons/account/views/account_full_reconcile_views.xml b/addons/account/views/account_full_reconcile_views.xml
index d96c83359b9..f1760e5d244 100644
--- a/addons/account/views/account_full_reconcile_views.xml
+++ b/addons/account/views/account_full_reconcile_views.xml
@@ -9,7 +9,7 @@