diff --git a/account_reset_chart/README.rst b/account_reset_chart/README.rst new file mode 100644 index 000000000..ae6382d8c --- /dev/null +++ b/account_reset_chart/README.rst @@ -0,0 +1,67 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :alt: License + +Account chart reset +=================== + +Adds a method to the company to remove its chart of accounts, including moves +and journals. By necessity, this process also removes the company's bank +accounts as they are linked to the company's journals and the company's payment +orders and payment modes if the payment module is installed. + +As a result, you can then reconfigure the company chart of account with the +same or a different chart template. + +Usage +===== + +To prevent major disasters when this module is installed, no interface is +provided. Please run through xmlrpc, for instance using erppeek: :: + + import erppeek + + host = 'localhost' + port = '8069' + admin_pw = 'admin' + dbname = 'openerp' + + client = erppeek.Client('http://%s:%s' % (host, port)) + client.login('admin', admin_pw, dbname) + client.execute('res.company', 'reset_chart', 1) + +Caution! This process will destroy + +Known issues / Roadmap +====================== + +This should work with the standard accounting modules installed. All sorts of +combinations with third party modules are imaginable that would require +modifications or extensions of the current implementation. + +Sequences are not reset during the process. + +Credits +======= + +Contributors +------------ + +* Stefan Rijnhart + +Icon courtesy of Alan Klim (CC-BY-20) - +https://www.flickr.com/photos/igraph/6469812927/ + +Maintainer +---------- + +.. image:: http://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: http://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit http://odoo-community.org. diff --git a/account_reset_chart/__init__.py b/account_reset_chart/__init__.py index 16e8b082f..0650744f6 100644 --- a/account_reset_chart/__init__.py +++ b/account_reset_chart/__init__.py @@ -1 +1 @@ -import model +from . import models diff --git a/account_reset_chart/__openerp__.py b/account_reset_chart/__openerp__.py index 7f75c3e83..1c979dbec 100644 --- a/account_reset_chart/__openerp__.py +++ b/account_reset_chart/__openerp__.py @@ -2,7 +2,7 @@ ############################################################################## # # Odoo, an open source suite of business apps -# This module copyright (C) 2014 Therp BV (). +# This module copyright (C) 2014-2015 Therp BV (). # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -20,39 +20,13 @@ ############################################################################## { "name": "Reset a chart of accounts", + "summary": ("Delete the accounting setup from an otherwise reusable " + "database"), "version": "1.0", - "author": "Therp BV", - "category": 'Partner', - "description": """ -Removes the current chart of accounts, including moves and journals. By -necessity, this process also removes the company's bank accounts as they -are linked to the company's journals and the company's payment orders -and payment modes if the payment module is installed. - -No interface is provided. Please run through xmlrpc, for instance using -erppeek: - - -import erppeek - -host = 'localhost' -port = '8069' -user_pw = 'admin' -dbname = 'openerp' - -client = erppeek.Client('http://%s:%s' % (host, port)) -client.login('admin', user_pw, dbname) -wiz_id = client.create('account.reset.chart', {'company_id': 1}) -client.AccountResetChart.reset_chart([wiz_id]) - - -Use with caution, obviously. - -Compatibility -============= -This module is compatible with Odoo 7.0. -""", + "author": "Therp BV,Odoo Community Association (OCA)", + "category": 'Accounting & Finance', "depends": [ 'account', ], + 'license': 'AGPL-3' } diff --git a/account_reset_chart/model/__init__.py b/account_reset_chart/model/__init__.py deleted file mode 100644 index 6c8649191..000000000 --- a/account_reset_chart/model/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import account_reset_chart diff --git a/account_reset_chart/model/account_reset_chart.py b/account_reset_chart/model/account_reset_chart.py deleted file mode 100644 index e4ec97a14..000000000 --- a/account_reset_chart/model/account_reset_chart.py +++ /dev/null @@ -1,127 +0,0 @@ -from openerp.osv import orm, fields -from openerp import netsvc -import logging - - -class ResetChart(orm.TransientModel): - _name = 'account.reset.chart' - _rec_name = 'company_id' - _columns = { - 'company_id': fields.many2one( - 'res.company', required=True), - } - - def reset_chart(self, cr, uid, ids, context=None): - logger = logging.getLogger('openerp.addons.account_reset_chart') - wkf_service = netsvc.LocalService('workflow') - wiz = self.browse(cr, uid, ids[0], context=context) - company_id = wiz.company_id.id - move_obj = self.pool['account.move'] - inv_obj = self.pool['account.invoice'] - inv_line_obj = self.pool['account.invoice.line'] - inv_tax_obj = self.pool['account.invoice.tax'] - - def unlink_from_company(model): - logger.info('Unlinking all records of model %s', model) - obj = self.pool.get(model) - if not obj: - logger.info('Model %s not found', model) - return - obj_ids = obj.search( - cr, uid, [('company_id', '=', company_id)], - context=context) - obj.unlink(cr, uid, obj_ids) - - payment_obj = self.pool.get('payment.order') - if payment_obj: - logger.info('Deleting payment orders.') - cr.execute( - """ - DELETE FROM payment_line - WHERE order_id IN ( - SELECT id FROM payment_order - WHERE company_id = %s); - """, (company_id,)) - cr.execute( - "DELETE FROM payment_order WHERE company_id = %s;", - (company_id,)) - - unlink_from_company('payment.mode') - - unlink_from_company('account.banking.account.settings') - unlink_from_company('res.partner.bank') - - logger.info('Undoing reconciliations') - rec_obj = self.pool['account.move.reconcile'] - rec_ids = rec_obj.search( - cr, uid, - [('line_id.move_id.company_id', '=', company_id)], context=context) - rec_obj.unlink(cr, uid, rec_ids, context=context) - - logger.info('Reset paid invoices\'s workflows') - paid_inv_ids = tuple(inv_obj.search( - cr, uid, - [('company_id', '=', company_id), ('state', '=', 'paid')], - context=context)) - if paid_inv_ids: - cr.execute( - """ - UPDATE wkf_instance - SET state = 'active' - WHERE res_type = 'account_invoice' - AND res_id IN %s""" % (paid_inv_ids,)) - cr.execute( - """ - UPDATE wkf_workitem - SET act_id = ( - SELECT res_id FROM ir_model_data - WHERE module = 'account' - AND name = 'act_open') - WHERE inst_id IN ( - SELECT id FROM wkf_instance - WHERE res_type = 'account_invoice' - AND res_id IN %s) - """ % (paid_inv_ids,)) - for inv_id in paid_inv_ids: - wkf_service.trg_validate( - uid, 'account.invoice', inv_id, 'invoice_cancel', cr) - - logger.info('Dismantling invoices') - inv_ids = inv_obj.search( - cr, uid, - [('company_id', '=', company_id)], - context=context) - inv_line_ids = inv_line_obj.search( - cr, uid, [('invoice_id', 'in', inv_ids)], context=context) - inv_line_obj.unlink(cr, uid, inv_line_ids, context=context) - inv_tax_ids = inv_tax_obj.search( - cr, uid, [('invoice_id', 'in', inv_ids)], context=context) - inv_tax_obj.unlink(cr, uid, inv_tax_ids, context=context) - logger.info('Unlinking invoices') - cr.execute( - """ - DELETE FROM account_invoice - WHERE id IN %s""" % (tuple(inv_ids),)) - - logger.info('Unlinking moves') - move_ids = move_obj.search( - cr, uid, [('company_id', '=', company_id)], - context=context) - cr.execute( - """UPDATE account_move SET state = 'draft' - WHERE id IN %s""" % (tuple(move_ids),)) - move_obj.unlink(cr, uid, move_ids, context=context) - - unlink_from_company('account.fiscal.position') - unlink_from_company('account.tax') - unlink_from_company('account.tax.code') - unlink_from_company('account.journal') - - logger.info('Unlink properties with account as values') - cr.execute( - """ - DELETE FROM ir_property - WHERE value_reference LIKE 'account.account,%%' - AND company_id = %s""" % (company_id,)) - unlink_from_company('account.account') - return True diff --git a/account_reset_chart/models/__init__.py b/account_reset_chart/models/__init__.py new file mode 100644 index 000000000..aff44f335 --- /dev/null +++ b/account_reset_chart/models/__init__.py @@ -0,0 +1 @@ +from . import res_company diff --git a/account_reset_chart/models/res_company.py b/account_reset_chart/models/res_company.py new file mode 100644 index 000000000..e888b45cc --- /dev/null +++ b/account_reset_chart/models/res_company.py @@ -0,0 +1,136 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Odoo, an open source suite of business apps +# This module copyright (C) 2014-2015 Therp BV (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +from openerp import api, models +import logging + + +class Company(models.Model): + _inherit = 'res.company' + + @api.one + def reset_chart(self): + logger = logging.getLogger('openerp.addons.account_reset_chart') + + def unlink_from_company(model): + logger.info('Unlinking all records of model %s for company %s', + model, self.name) + try: + obj = self.env[model] + except KeyError: + logger.info('Model %s not found', model) + return + records = obj.search([('company_id', '=', self.id)]) + if records: # account_account.unlink() breaks on empty id list + records.unlink() + + self.env['account.journal'].search( + [('company_id', '=', self.id)]).write({'update_posted': True}) + statements = self.env['account.bank.statement'].search( + [('company_id', '=', self.id)]) + statements.button_cancel() + statements.unlink() + + try: + self.env['payment.order'] + logger.info('Deleting payment orders.') + self._cr.execute( + """ + DELETE FROM payment_line + WHERE order_id IN ( + SELECT id FROM payment_order + WHERE company_id = %s); + """, (self.id,)) + self._cr.execute( + "DELETE FROM payment_order WHERE company_id = %s;", + (self.id,)) + + unlink_from_company('payment.mode') + except KeyError: + pass + + unlink_from_company('account.banking.account.settings') + unlink_from_company('res.partner.bank') + + logger.info('Undoing reconciliations') + rec_obj = self.env['account.move.reconcile'] + rec_obj.search( + [('line_id.company_id', '=', self.id)]).unlink() + + logger.info('Reset paid invoices\'s workflows') + paid_invoices = self.env['account.invoice'].search( + [('company_id', '=', self.id), ('state', '=', 'paid')]) + if paid_invoices: + self._cr.execute( + """ + UPDATE wkf_instance + SET state = 'active' + WHERE res_type = 'account_invoice' + AND res_id IN %s""" % (tuple(paid_invoices.ids),)) + self._cr.execute( + """ + UPDATE wkf_workitem + SET act_id = ( + SELECT res_id FROM ir_model_data + WHERE module = 'account' + AND name = 'act_open') + WHERE inst_id IN ( + SELECT id FROM wkf_instance + WHERE res_type = 'account_invoice' + AND res_id IN %s) + """ % (tuple(paid_invoices.ids),)) + paid_invoices.signal_workflow('invoice_cancel') + + logger.info('Dismantling invoices') + inv_ids = self.env['account.invoice'].search( + [('company_id', '=', self.id)]).ids + if inv_ids: + self.env['account.invoice.line'].search( + [('invoice_id', 'in', inv_ids)]).unlink() + self.env['account.invoice.tax'].search( + [('invoice_id', 'in', inv_ids)]).unlink() + logger.info('Unlinking invoices') + self._cr.execute( + """ + DELETE FROM account_invoice + WHERE id IN %s""", (tuple(inv_ids),)) + + logger.info('Unlinking moves') + moves = self.env['account.move'].search([('company_id', '=', self.id)]) + if moves: + self._cr.execute( + """UPDATE account_move SET state = 'draft' + WHERE id IN %s""", (tuple(moves.ids),)) + moves.unlink() + + unlink_from_company('account.fiscal.position') + unlink_from_company('account.tax') + unlink_from_company('account.tax.code') + unlink_from_company('account.journal') + + logger.info('Unlink properties with account as values') + self._cr.execute( + """ + DELETE FROM ir_property + WHERE value_reference LIKE 'account.account,%%' + AND company_id = %s""", (self.id,)) + unlink_from_company('account.account') + return True