[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:
@@ -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")
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user