diff --git a/addons/l10n_fr_certification/__init__.py b/addons/l10n_fr_certification/__init__.py deleted file mode 100644 index dcd82374562..00000000000 --- a/addons/l10n_fr_certification/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from . import models -from odoo import api, SUPERUSER_ID - -def _setup_inalterability(cr, registry): - env = api.Environment(cr, SUPERUSER_ID, {}) - # enable ping for this module - env['publisher_warranty.contract'].update_notification(cron_mode=True) - - # make sure account_cancel is not usable at the same time as l10n_fr - # FORWARD PORT NOTICE - # In master as of March 2017, RCO-ODOO coded an exclusive field on modules to flag incompatibility - - wanted_states = ['installed', 'to upgrade', 'to install'] - account_cancel_module = env['ir.module.module'].search([('name', '=', 'account_cancel')], limit=1) - - if account_cancel_module and account_cancel_module.state in wanted_states: - views_xml_id = env['ir.model.data'].search([('module', '=', 'account_cancel'), ('model', '=', 'ir.ui.view')]) - ir_views = env['ir.ui.view'].browse([v.res_id for v in views_xml_id]) - for cancel_view in ir_views: - cancel_view.write({'active': False}) - - fr_companies = env['res.company'].search([('partner_id.country_id.code', '=', 'FR')]) - if fr_companies: - # create the securisation sequence per company - fr_companies._create_secure_sequence() - - #reset the update_posted field on journals - journals = env['account.journal'].search([('company_id', 'in', fr_companies.ids)]) - for journal in journals: - journal.write({'update_posted': False}) diff --git a/addons/l10n_fr_certification/__manifest__.py b/addons/l10n_fr_certification/__manifest__.py deleted file mode 100644 index 7e2ea327d54..00000000000 --- a/addons/l10n_fr_certification/__manifest__.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -{ - 'name': 'France - Accounting - Certified CGI 286 I-3 bis', - 'version': '1.0', - 'category': 'Localization', - 'description': """ -""", - 'depends': ['l10n_fr'], - 'installable': True, - 'auto_install': False, - 'application': True, - 'data': [ - 'views/no_cancel.xml', - 'data/account_move.xml', - ], - 'post_init_hook': '_setup_inalterability', -} diff --git a/addons/l10n_fr_certification/data/account_move.xml b/addons/l10n_fr_certification/data/account_move.xml deleted file mode 100644 index 95adc8d8c2e..00000000000 --- a/addons/l10n_fr_certification/data/account_move.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - Intégrité des données comptables - - ir.actions.server - - action = env['account.move']._check_hash_integrity(env.user.company_id.id) - - - - - - diff --git a/addons/l10n_fr_certification/models/__init__.py b/addons/l10n_fr_certification/models/__init__.py deleted file mode 100644 index 8472b2f8bfb..00000000000 --- a/addons/l10n_fr_certification/models/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from . import res_company -from . import account diff --git a/addons/l10n_fr_certification/models/account.py b/addons/l10n_fr_certification/models/account.py deleted file mode 100644 index bef0880d7ca..00000000000 --- a/addons/l10n_fr_certification/models/account.py +++ /dev/null @@ -1,155 +0,0 @@ -# -*- coding: utf-8 -*- -from hashlib import sha256 -from json import dumps - -from openerp import models, api, fields -from openerp.tools.translate import _ -from openerp.exceptions import UserError - -ERR_MSG = _("According to the french law, you cannot modify a %s in order for its posted data to be updated or deleted. Field: %s") - -#forbidden fields -MOVE_FIELDS = ['date', 'journal_id', 'company_id'] -LINE_FIELDS = ['debit', 'credit', 'account_id', 'move_id', 'partner_id'] - - -class AccountMove(models.Model): - _inherit = "account.move" - - l10n_fr_secure_sequence_number = fields.Integer(readonly=True, copy=False) - l10n_fr_hash = fields.Char(readonly=True, copy=False) - l10n_fr_string_to_hash = fields.Char(compute='_compute_string_to_hash', readonly=True, store=False) - - def _get_new_hash(self, secure_seq_number): - """ Returns the hash to write on journal entries when they get posted""" - self.ensure_one() - #get the only one exact previous move in the securisation sequence - prev_move = self.search([('state', '=', 'posted'), - ('company_id', '=', self.company_id.id), - ('l10n_fr_secure_sequence_number', '=', int(secure_seq_number) - 1)]) - if prev_move and len(prev_move) != 1: - raise UserError( - _('Error occured when computing the hash. Impossible to get the unique previous posted move')) - - #build and return the hash - return self._compute_hash(prev_move.l10n_fr_hash if prev_move else '') - - def _compute_hash(self, previous_hash): - """ Computes the hash of the browse_record given as self, based on the hash - of the previous record in the company's securisation sequence given as parameter""" - self.ensure_one() - hash_string = sha256(previous_hash + self.l10n_fr_string_to_hash) - return hash_string.hexdigest() - - def _compute_string_to_hash(self): - def _getattrstring(obj, field_str): - field_value = obj[field_str] - if obj._fields[field_str].type == 'many2one': - field_value = field_value.id - return str(field_value) - - for move in self: - values = {} - for field in MOVE_FIELDS: - values[field] = _getattrstring(move, field) - - for line in move.line_ids: - for field in LINE_FIELDS: - values[field] = _getattrstring(line, field) - #make the json serialization canonical - # (https://tools.ietf.org/html/draft-staykov-hu-json-canonical-form-00) - move.l10n_fr_string_to_hash = dumps(values, sort_keys=True, encoding="utf-8", - ensure_ascii=True, indent=None, - separators=(',',':')) - - @api.multi - def write(self, vals): - has_been_posted = False - for move in self: - if move.company_id.country_id.code == 'FR': - # write the hash and the secure_sequence_number when posting an account.move - if vals.get('state') == 'posted': - has_been_posted = True - - # restrict the operation in case we are trying to write a forbidden field - if (move.state == "posted" and set(vals).intersection(MOVE_FIELDS)): - raise UserError(ERR_MSG % (self._name, ', '.join(MOVE_FIELDS))) - # restrict the operation in case we are trying to overwrite existing hash - if (move.l10n_fr_hash and 'l10n_fr_hash' in vals) or (move.l10n_fr_secure_sequence_number and 'l10n_fr_secure_sequence_number' in vals): - raise UserError(_('You cannot overwrite the values ensuring the inalterability of the accounting.')) - res = super(AccountMove, self).write(vals) - # write the hash and the secure_sequence_number when posting an account.move - if has_been_posted: - for move in self.filtered(lambda m: m.company_id.country_id.code == 'FR' and - not (m.l10n_fr_secure_sequence_number or m.l10n_fr_hash)): - new_number = move.company_id.l10n_fr_secure_sequence_id.next_by_id() - vals_hashing = {'l10n_fr_secure_sequence_number': new_number, - 'l10n_fr_hash': move._get_new_hash(new_number)} - res |= super(AccountMove, move).write(vals_hashing) - return res - - def button_cancel(self): - #by-pass the normal behavior/message that tells people can cancel a posted journal entry - #if the journal allows it. - if self.company_id.country_id.code == 'FR': - raise UserError(_('You cannot modify a posted entry of a journal.')) - super(AccountMove, self).button_cancel() - - @api.model - def _check_hash_integrity(self, company_id): - """Checks that all posted moves have still the same data as when they were posted - and raises an error with the result. - """ - moves = self.search([('state', '=', 'posted'), - ('company_id', '=', company_id), - ('l10n_fr_secure_sequence_number', '!=', 0)], - order="l10n_fr_secure_sequence_number ASC") - - if not moves: - raise UserError(_('Warning: impossible to find any move with a hash.')) - previous_hash = '' - start_move_info = [] - for move in moves: - if move.l10n_fr_hash != move._compute_hash(previous_hash=previous_hash): - raise UserError(_('Corrupted Data on move %s.') % move.id) - if not previous_hash: - #save the date and sequence number of the first move hashed - start_move_info = [move.date, move.l10n_fr_secure_sequence_number] - previous_hash = move.l10n_fr_hash - end_move_info = [move.date, move.l10n_fr_secure_sequence_number] - raise UserError(_('''Successfully checked the integrity of account moves. - - The account moves are guaranteed to be in their original and inalterable state - - since: %s (Sequence Number: %s) - - to: %s (Sequence Number: %s)''' - ) % (start_move_info[0], start_move_info[1], end_move_info[0], end_move_info[1])) - - -class AccountMoveLine(models.Model): - _inherit = "account.move.line" - - @api.multi - def write(self, vals): - # restrict the operation in case we are trying to write a forbidden field - if set(vals).intersection(LINE_FIELDS): - if any(l.company_id.country_id.code == 'FR' and l.move_id.state == 'posted' for l in self): - raise UserError(ERR_MSG % (self._name, ', '.join(LINE_FIELDS))) - return super(AccountMoveLine, self).write(vals) - - -class AccountJournal(models.Model): - _inherit = "account.journal" - - @api.multi - def write(self, vals): - # restrict the operation in case we are trying to write a forbidden field - if self.company_id.country_id.code == 'FR' and vals.get('update_posted'): - raise UserError(ERR_MSG % (self._name, 'update_posted')) - return super(AccountJournal, self).write(vals) - - @api.model - def create(self, vals): - # restrict the operation in case we are trying to set a forbidden field - if self.company_id.country_id.code == 'FR' and vals.get('update_posted'): - raise UserError(ERR_MSG % (self._name, 'update_posted')) - return super(AccountJournal, self).create(vals) diff --git a/addons/l10n_fr_certification/models/res_company.py b/addons/l10n_fr_certification/models/res_company.py deleted file mode 100644 index 66a2a633164..00000000000 --- a/addons/l10n_fr_certification/models/res_company.py +++ /dev/null @@ -1,42 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from openerp import fields, models, api - -class ResCompany(models.Model): - _inherit = 'res.company' - - l10n_fr_secure_sequence_id = fields.Many2one('ir.sequence', 'Sequence to use to ensure the securisation of data', readonly=True) - - @api.model - def create(self, vals): - company = super(ResCompany, self).create(vals) - #when creating a new french company, create the securisation sequence as well - if company.country_id == self.env.ref('base.fr'): - company._create_secure_sequence() - return company - - @api.multi - def write(self, vals): - res = super(ResCompany, self).write(vals) - #if country changed to fr, create the securisation sequence - if vals.get('country_id') and vals.get('country_id') == self.env.ref('base.fr').id: - self.filtered(lambda c: not c.l10n_fr_secure_sequence_id)._create_secure_sequence() - return res - - def _create_secure_sequence(self): - """This function creates a no_gap sequence on each companies in self that will ensure - a unique number is given to all posted account.move in such a way that we can always - find the previous move of a journal entry. - """ - for company in self: - vals = { - 'name': 'French Securisation of account_move_line - ' + company.name, - 'code': 'FRSECUR', - 'implementation': 'no_gap', - 'prefix': '', - 'suffix': '', - 'padding': 0, - 'company_id': company.id} - seq = self.env['ir.sequence'].create(vals) - company.write({'l10n_fr_secure_sequence_id': seq.id}) diff --git a/addons/l10n_fr_certification/views/no_cancel.xml b/addons/l10n_fr_certification/views/no_cancel.xml deleted file mode 100644 index 43bc66d6ee3..00000000000 --- a/addons/l10n_fr_certification/views/no_cancel.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - bank.statement.draft.form.inherit - account.bank.statement - - - - 1 - - - - - - invoice.form.cancel.inherit - account.invoice - - - - 1 - - - - - - account.move.form.nocancel.inherit - account.move - - - - 1 - - - - - -