[IMP] account: detect holes in sequences

The resequence feature and the possibility to edit the invoice number in
draft are powerful flexibility tools. Though, misusing them can lead to
holes in a sequence that can be hard to detect.

This task aims at providing alerts on the dashboard (on Sales and
Purchases journals' cards) highlighting the holes in the sections

_____________

Suppose the following sequence on a journal

BILL/2022/01/0001
BILL/2022/01/0002
BILL/2022/01/0004

> BILL/2022/01/0003 is missing.

Then show a link on this journal's card in the kanban view of journals
(accounting dashboard) mentioning.

> Check the sequence

Pressing the button opens the list view of bills on this specific
journal.

BILL/2022/01/0004 appears in red ; since it is after a hole

task-2767272

closes odoo/odoo#84788

Signed-off-by: Laurent Smet <las@odoo.com>
This commit is contained in:
william
2022-04-01 12:31:03 +02:00
parent dba9cd97ef
commit 08ef4e9223
4 changed files with 79 additions and 6 deletions
@@ -72,6 +72,26 @@ class account_journal(models.Model):
show_on_dashboard = fields.Boolean(string='Show journal on dashboard', help="Whether this journal should be displayed on the dashboard or not", default=True)
color = fields.Integer("Color Index", default=0)
entries_count = fields.Integer(compute='_compute_entries_count')
has_sequence_holes = fields.Boolean(compute='_compute_has_sequence_holes')
def _query_has_sequence_holes(self):
self.env.cr.execute("""
SELECT move.journal_id,
move.sequence_prefix
FROM account_move move
WHERE move.journal_id = ANY(%(journal_ids)s)
AND move.state = 'posted'
GROUP BY move.journal_id, move.sequence_prefix
HAVING COUNT(*) != MAX(move.sequence_number) - MIN(move.sequence_number) + 1
""", {
'journal_ids': self.ids,
})
return self.env.cr.fetchall()
def _compute_has_sequence_holes(self):
has_sequence_holes = set(journal_id for journal_id, _prefix in self._query_has_sequence_holes())
for journal in self:
journal.has_sequence_holes = journal.id in has_sequence_holes
def _compute_entries_count(self):
res = {
@@ -567,6 +587,23 @@ class account_journal(models.Model):
)
return action
def show_sequence_holes(self):
has_sequence_holes = self._query_has_sequence_holes()
return {
'type': 'ir.actions.act_window',
'name': _("Journal Entries"),
'res_model': 'account.move',
'view_mode': 'list,form',
'domain': expression.OR(
[('journal_id', '=', journal_id), ('sequence_prefix', '=', prefix)]
for journal_id, prefix in has_sequence_holes
),
'context': {
'search_default_group_by_sequence_prefix': 1,
'expand': 1,
}
}
def create_bank_statement(self):
"""return action to create a bank statements. This button should be called only on journals with type =='bank'"""
action = self.env["ir.actions.actions"]._for_xml_id("account.action_bank_statement_tree")
+23 -3
View File
@@ -143,6 +143,7 @@ class AccountMove(models.Model):
# ==== Business fields ====
name = fields.Char(string='Number', copy=False, compute='_compute_name', readonly=False, store=True, index='btree', tracking=True)
highest_name = fields.Char(compute='_compute_highest_name')
made_sequence_hole = fields.Boolean(compute='_compute_made_sequence_hole')
show_name_warning = fields.Boolean(store=False)
date = fields.Date(
string='Date',
@@ -1189,6 +1190,25 @@ class AccountMove(models.Model):
for record in self:
record.highest_name = record._get_last_sequence()
@api.depends('name', 'journal_id')
def _compute_made_sequence_hole(self):
self.env.cr.execute("""
SELECT this.id
FROM account_move this
LEFT JOIN account_move other ON this.journal_id = other.journal_id
AND this.sequence_prefix = other.sequence_prefix
AND this.sequence_number = other.sequence_number + 1
WHERE other.id IS NULL
AND this.sequence_number != 1
AND this.name != '/'
AND this.id = ANY(%(move_ids)s)
""", {
'move_ids': self.ids,
})
made_sequence_hole = set(r[0] for r in self.env.cr.fetchall())
for move in self:
move.made_sequence_hole = move.id in made_sequence_hole
@api.onchange('name', 'highest_name')
def _onchange_name_warning(self):
if self.name and self.name != '/' and self.name <= (self.highest_name or ''):
@@ -1786,10 +1806,10 @@ class AccountMove(models.Model):
WHERE line.move_id IN %s
GROUP BY line.move_id, currency.decimal_places
)
SELECT *
SELECT *
FROM error_moves
WHERE balance !=0
OR negative_normal
WHERE balance !=0
OR negative_normal
OR positive_storno
''', [tuple(self.ids)])
@@ -24,6 +24,10 @@
<t t-if="(journal_type == 'bank' || journal_type == 'cash')" t-call="JournalBodyBankCash"/>
<t t-if="journal_type == 'sale' || journal_type == 'purchase'" t-call="JournalBodySalePurchase"/>
<t t-if="journal_type == 'general'" t-call="JournalMiscelaneous"/>
<div class="col-12 col-sm-5 mb-3 mb-sm-0 o_kanban_primary_left"/>
<div class="col-12 col-sm-7 o_kanban_primary_right">
<t t-call="HasSequenceHoles"/>
</div>
</div>
<t t-if="journal_type == 'bank' || journal_type == 'cash' || journal_type == 'sale' || journal_type == 'purchase'" t-call="JournalBodyGraph"/>
</div><div class="container o_kanban_card_manage_pane dropdown-menu" role="menu">
@@ -51,6 +55,14 @@
</div>
</t>
<t t-name="HasSequenceHoles">
<field name="has_sequence_holes" invisible="1"/>
<a t-if="record.has_sequence_holes.raw_value" name="show_sequence_holes" type="object" class="text-warning">
<i class="fa fa-exclamation-triangle"/>
Holes in the sequence
</a>
</t>
<t t-name="JournalManage">
<!-- For bank and cash -->
+7 -3
View File
@@ -400,9 +400,10 @@
<field name="name">account.move.tree</field>
<field name="model">account.move</field>
<field name="arch" type="xml">
<tree string="Journal Entries" sample="1" decoration-info="state == 'draft'">
<tree string="Journal Entries" sample="1" decoration-info="state == 'draft'" expand="context.get('expand', False)">
<field name="made_sequence_hole" invisible="1"/>
<field name="date"/>
<field name="name"/>
<field name="name" decoration-danger="made_sequence_hole"/>
<field name="partner_id" optional="show"/>
<field name="ref" optional="show"/>
<field name="journal_id"/>
@@ -422,13 +423,15 @@
<tree string="Invoices"
js_class="account_tree"
decoration-info="state == 'draft'"
expand="context.get('expand', False)"
sample="1">
<header>
<button name="action_register_payment" type="object" string="Register Payment"
groups="account.group_account_user"
invisible="context.get('default_move_type') not in ('out_invoice', 'out_refund', 'out_receipt', 'in_invoice', 'in_refund','in_receipt')"/>
</header>
<field name="name" decoration-bf="1"/>
<field name="made_sequence_hole" invisible="1"/>
<field name="name" decoration-bf="1" decoration-danger="made_sequence_hole"/>
<field name="partner_id" invisible="1"/>
<field name="invoice_source_email" invisible="1"/>
<field name="invoice_partner_display_name" invisible="context.get('default_move_type') not in ('in_invoice', 'in_refund','in_receipt')" groups="base.group_user" string="Vendor" />
@@ -1363,6 +1366,7 @@
<separator/>
<filter string="Invoice Date" name="invoicedate" context="{'group_by': 'invoice_date'}"/>
<filter string="Due Date" name="duedate" context="{'group_by': 'invoice_date_due'}"/>
<filter string="Sequence Prefix" name="group_by_sequence_prefix" context="{'group_by': 'sequence_prefix'}" invisible="1"/>
</group>
</search>
</field>