Order move for hash: In the method write, we sort the recordset following this rule: by date, by ref and by id. This rule is used because we need to be consistent with the method _compute_name. If we don't filter moves like that, we could possibly have a problem. If you have several moves and you post them at the same moment with the action "Post entries", the hash sequence could be in another order than the name sequence. For example: Move_20 (29/07/2020) - Hash_2 Move_21 (02/07/2020) - Hash_1 About security check, if the hash is activated, the user cannot reorder by date with resequence because we don't want that the name sequence have not the same order than hash sequence. Task ID: 2297407 closes odoo/odoo#55669 X-original-commit: b3ae96dc52178a3b13dda845244fe34268aed8a0 Signed-off-by: Cedric Snauwaert (csn) <csn@openerp.com>
140 lines
7.3 KiB
Python
140 lines
7.3 KiB
Python
# -*- coding: utf-8 -*-
|
|
from odoo import api, fields, models, _
|
|
from odoo.exceptions import UserError
|
|
from odoo.tools.date_utils import get_month, get_fiscal_year
|
|
from odoo.tools.misc import format_date
|
|
|
|
import re
|
|
from collections import defaultdict
|
|
import json
|
|
|
|
|
|
class ReSequenceWizard(models.TransientModel):
|
|
_name = 'account.resequence.wizard'
|
|
_description = 'Remake the sequence of Journal Entries.'
|
|
|
|
sequence_number_reset = fields.Char(compute='_compute_sequence_number_reset')
|
|
first_date = fields.Date(help="Date (inclusive) from which the numbers are resequenced.")
|
|
end_date = fields.Date(help="Date (inclusive) to which the numbers are resequenced. If not set, all Journal Entries up to the end of the period are resequenced.")
|
|
first_name = fields.Char(compute="_compute_first_name", readonly=False, store=True, required=True, string="First New Sequence")
|
|
ordering = fields.Selection([('keep', 'Keep current order'), ('date', 'Reorder by accounting date')], required=True, default='keep')
|
|
move_ids = fields.Many2many('account.move')
|
|
new_values = fields.Text(compute='_compute_new_values')
|
|
preview_moves = fields.Text(compute='_compute_preview_moves')
|
|
|
|
@api.model
|
|
def default_get(self, fields_list):
|
|
values = super(ReSequenceWizard, self).default_get(fields_list)
|
|
active_move_ids = self.env['account.move']
|
|
if self.env.context['active_model'] == 'account.move' and 'active_ids' in self.env.context:
|
|
active_move_ids = self.env['account.move'].browse(self.env.context['active_ids'])
|
|
if len(active_move_ids.journal_id) > 1:
|
|
raise UserError(_('You can only resequence items from the same journal'))
|
|
if active_move_ids.journal_id.refund_sequence and len(set(active_move_ids.mapped('move_type')) - {'out_receipt', 'in_receipt'}) > 1:
|
|
raise UserError(_('The sequences of this journal are different for Invoices and Refunds but you selected some of both types.'))
|
|
values['move_ids'] = [(6, 0, active_move_ids.ids)]
|
|
return values
|
|
|
|
@api.depends('first_name')
|
|
def _compute_sequence_number_reset(self):
|
|
for record in self:
|
|
record.sequence_number_reset = record.move_ids[0]._deduce_sequence_number_reset(record.first_name)
|
|
|
|
@api.depends('move_ids')
|
|
def _compute_first_name(self):
|
|
self.first_name = ""
|
|
for record in self:
|
|
if record.move_ids:
|
|
record.first_name = min(record.move_ids._origin.mapped('name'))
|
|
|
|
@api.depends('new_values', 'ordering')
|
|
def _compute_preview_moves(self):
|
|
"""Reduce the computed new_values to a smaller set to display in the preview."""
|
|
for record in self:
|
|
new_values = sorted(json.loads(record.new_values).values(), key=lambda x: x['server-date'], reverse=True)
|
|
changeLines = []
|
|
in_elipsis = 0
|
|
previous_line = None
|
|
for i, line in enumerate(new_values):
|
|
if i < 3 or i == len(new_values) - 1 or line['new_by_name'] != line['new_by_date'] \
|
|
or (self.sequence_number_reset == 'year' and line['server-date'][0:4] != previous_line['server-date'][0:4])\
|
|
or (self.sequence_number_reset == 'month' and line['server-date'][0:7] != previous_line['server-date'][0:7]):
|
|
if in_elipsis:
|
|
changeLines.append({'current_name': '... (%s other)' % str(in_elipsis), 'new_by_name': '...', 'new_by_date': '...', 'date': '...'})
|
|
in_elipsis = 0
|
|
changeLines.append(line)
|
|
else:
|
|
in_elipsis += 1
|
|
previous_line = line
|
|
|
|
record.preview_moves = json.dumps({
|
|
'ordering': record.ordering,
|
|
'changeLines': changeLines,
|
|
})
|
|
|
|
@api.depends('first_name', 'move_ids', 'sequence_number_reset')
|
|
def _compute_new_values(self):
|
|
"""Compute the proposed new values.
|
|
|
|
Sets a json string on new_values representing a dictionary thats maps account.move
|
|
ids to a disctionay containing the name if we execute the action, and information
|
|
relative to the preview widget.
|
|
"""
|
|
def _get_move_key(move_id):
|
|
if self.sequence_number_reset == 'year':
|
|
return move_id.date.year
|
|
elif self.sequence_number_reset == 'month':
|
|
return (move_id.date.year, move_id.date.month)
|
|
return 'default'
|
|
|
|
self.new_values = "{}"
|
|
for record in self.filtered('first_name'):
|
|
moves_by_period = defaultdict(lambda: record.env['account.move'])
|
|
for move in record.move_ids._origin: # Sort the moves by period depending on the sequence number reset
|
|
moves_by_period[_get_move_key(move)] += move
|
|
|
|
format, format_values = self.env['account.move']._get_sequence_format_param(record.first_name)
|
|
|
|
new_values = {}
|
|
for j, period_recs in enumerate(moves_by_period.values()):
|
|
# compute the new values period by period
|
|
for move in period_recs:
|
|
new_values[move.id] = {
|
|
'current_name': move.name,
|
|
'state': move.state,
|
|
'date': format_date(self.env, move.date),
|
|
'server-date': str(move.date),
|
|
}
|
|
|
|
new_name_list = [format.format(**{
|
|
**format_values,
|
|
'year': period_recs[0].date.year % (10 ** format_values['year_length']),
|
|
'month': period_recs[0].date.month,
|
|
'seq': i + (format_values['seq'] if j == (len(moves_by_period)-1) else 1),
|
|
}) for i in range(len(period_recs))]
|
|
|
|
# For all the moves of this period, assign the name by increasing initial name
|
|
for move, new_name in zip(period_recs.sorted(lambda m: (m.sequence_prefix, m.sequence_number)), new_name_list):
|
|
new_values[move.id]['new_by_name'] = new_name
|
|
# For all the moves of this period, assign the name by increasing date
|
|
for move, new_name in zip(period_recs.sorted(lambda m: (m.date, m.name, m.id)), new_name_list):
|
|
new_values[move.id]['new_by_date'] = new_name
|
|
|
|
record.new_values = json.dumps(new_values)
|
|
|
|
def resequence(self):
|
|
new_values = json.loads(self.new_values)
|
|
# Can't change the name of a posted invoice, but we do not want to have the chatter
|
|
# logging 3 separate changes with [state to draft], [change of name], [state to posted]
|
|
self.with_context(tracking_disable=True).move_ids.state = 'draft'
|
|
if self.move_ids.journal_id and self.move_ids.journal_id.restrict_mode_hash_table:
|
|
if self.ordering == 'date':
|
|
raise UserError(_('You can not reorder sequence by date when the journal is locked with a hash.'))
|
|
for move_id in self.move_ids:
|
|
if str(move_id.id) in new_values:
|
|
if self.ordering == 'keep':
|
|
move_id.name = new_values[str(move_id.id)]['new_by_name']
|
|
else:
|
|
move_id.name = new_values[str(move_id.id)]['new_by_date']
|
|
move_id.with_context(tracking_disable=True).state = new_values[str(move_id.id)]['state']
|