[IMP] account: journal items match improvements

This commit contains some misc improvements based on the 16.0 overall improvements regarding navigation and Journal Items

in account.full.reconcile, 'name' field has been removed, and the replacement for matching_number will be the SQL id instead (no more sequences)

For full reconciliations, the matching number now don't have an 'A' prefix
For partial reconciliations, the matching number will have a format of 'P{id}'

Community-pr: https://github.com/odoo/odoo/pull/138140
Enterprise-pr: https://github.com/odoo/enterprise/pull/48980
Task-id: 3530458
Part-of: odoo/odoo#138140
This commit is contained in:
william-andre
2023-10-25 11:37:23 +00:00
parent d1c7e37149
commit 30a37c10ec
8 changed files with 175 additions and 35 deletions
-12
View File
@@ -97,18 +97,6 @@
Command.create({'value': 'percent', 'value_amount': 100.0, 'nb_days': 30})]"/>
</record>
<!--
Account Statement Sequences
-->
<record id="sequence_reconcile_seq" model="ir.sequence">
<field name="name">Account reconcile sequence</field>
<field name="code">account.reconcile</field>
<field name="prefix">A</field>
<field eval="1" name="number_next"/>
<field eval="1" name="number_increment"/>
<field eval="False" name="company_id"/>
</record>
<!-- Account-related subtypes for messaging / Chatter -->
<record id="mt_invoice_validated" model="mail.message.subtype">
<field name="name">Validated</field>
@@ -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
+17 -18
View File
@@ -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()
@@ -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
# -------------------------------------------------------------------------
@@ -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.
@@ -9,7 +9,7 @@
<form string="Matching">
<group>
<div class="oe_title" colspan="4">
<h1><field name="name" readonly="1"/></h1>
<h1><field name="id" readonly="1"/></h1>
</div>
<separator string="Matched Journal Items" colspan="4"/>
<field name="reconciled_line_ids" readonly="1" colspan="4" nolabel="1"/>
+1 -1
View File
@@ -360,7 +360,7 @@
<filter string="Invoice Date" name="groupby_invoice_date" domain="[]" context="{'group_by': 'invoice_date'}"/>
<filter string="Taxes" name="group_by_taxes" domain="[]" context="{'group_by': 'tax_ids'}"/>
<filter string="Tax Grid" name="group_by_tax_tags" domain="[]" context="{'group_by': 'tax_tag_ids'}"/>
<filter string="Matching #" name="group_by_matching" domain="[]" context="{'group_by': 'full_reconcile_id'}"/>
<filter string="Matching #" name="group_by_matching" domain="[]" context="{'group_by': 'matching_number'}"/>
</group>
<searchpanel class="account_root">
<field name="account_root_id" icon="fa-filter" groupby="account_id" limit="0"/>
+1 -1
View File
@@ -337,7 +337,7 @@ class AccountFrFec(models.TransientModel):
ELSE REGEXP_REPLACE(replace(aml.name, '|', '/'), '[\\t\\n\\r]', ' ', 'g') END AS EcritureLib,
replace(CASE WHEN aml.debit = 0 THEN '0,00' ELSE to_char(aml.debit, '000000000000000D99') END, '.', ',') AS Debit,
replace(CASE WHEN aml.credit = 0 THEN '0,00' ELSE to_char(aml.credit, '000000000000000D99') END, '.', ',') AS Credit,
CASE WHEN rec.name IS NULL THEN '' ELSE rec.name END AS EcritureLet,
CASE WHEN rec.id IS NULL THEN ''::text ELSE rec.id::text END AS EcritureLet,
CASE WHEN aml.full_reconcile_id IS NULL THEN '' ELSE TO_CHAR(rec.create_date, 'YYYYMMDD') END AS DateLet,
TO_CHAR(am.date, 'YYYYMMDD') AS ValidDate,
CASE