diff --git a/account_reversal/__init__.py b/account_reversal/__init__.py new file mode 100644 index 000000000..7966fb050 --- /dev/null +++ b/account_reversal/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Account reversal module for OpenERP +# Copyright (C) 2011 Akretion (http://www.akretion.com). All Rights Reserved +# @author Alexis de Lattre +# +# 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 . +# +############################################################################## + +import account_reversal +import wizard diff --git a/account_reversal/__openerp__.py b/account_reversal/__openerp__.py new file mode 100644 index 000000000..48abefbc8 --- /dev/null +++ b/account_reversal/__openerp__.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Account reversal module for OpenERP +# Copyright (C) 2011 Akretion (http://www.akretion.com). All Rights Reserved +# @author Alexis de Lattre +# Copyright 2012-2013 Camptocamp SA +# @author Guewen Baconnier (Camptocamp) +# +# 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 . +# +############################################################################## + + +{ + 'name': 'Account Reversal', + 'version': '1.0', + 'category': 'Generic Modules/Accounting', + 'license': 'AGPL-3', + 'description': """ +Account Reversal +================ + +This module adds an action "Reversal" on account moves, +to allow the accountant to create reversal account moves in 2 clicks. +Also add on account entries: + - a checkbox and filter "to be reversed" + - a link between an entry and its reversal entry + +Module originally developped by Alexis de Lattre +during the Akretion-Camptocamp code sprint of June 2011. + +""", + 'author': 'Akretion,Camptocamp', + 'website': 'http://www.akretion.com/', + 'depends': ['account'], + 'data': [ + 'account_view.xml', + 'wizard/account_move_reverse_view.xml' + ], + 'installable': True, + 'active': False, +} diff --git a/account_reversal/account_reversal.py b/account_reversal/account_reversal.py new file mode 100644 index 000000000..5be398822 --- /dev/null +++ b/account_reversal/account_reversal.py @@ -0,0 +1,154 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Account reversal module for OpenERP +# Copyright (C) 2011 Akretion (http://www.akretion.com). All Rights Reserved +# @author Alexis de Lattre +# with the kind advice of Nicolas Bessi from Camptocamp +# Copyright (C) 2012-2013 Camptocamp SA (http://www.camptocamp.com) +# @author Guewen Baconnier +# +# 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.osv import fields, orm + + +class account_move(orm.Model): + _inherit = "account.move" + + _columns = { + 'to_be_reversed': fields.boolean( + 'To Be Reversed', + help='Check this box if your entry has to be' + 'reversed at the end of period.'), + 'reversal_id': fields.many2one( + 'account.move', + 'Reversal Entry', + ondelete='set null', + readonly=True), + } + + def _move_reversal(self, cr, uid, move, reversal_date, + reversal_period_id=False, reversal_journal_id=False, + move_prefix=False, move_line_prefix=False, + context=None): + """ + Create the reversal of a move + + :param move: browse instance of the move to reverse + :param reversal_date: when the reversal must be input + :param reversal_period_id: facultative period to write on the move + (use the period of the date if empty + :param reversal_journal_id: facultative journal on which create + the move + :param move_prefix: prefix for the move's name + :param move_line_prefix: prefix for the move line's names + + :return: Returns the id of the created reversal move + """ + if context is None: + context = {} + move_line_obj = self.pool.get('account.move.line') + period_obj = self.pool.get('account.period') + period_ctx = context.copy() + period_ctx['company_id'] = move.company_id.id + period_ctx['account_period_prefer_normal'] = True + + if not reversal_period_id: + reversal_period_id = period_obj.find( + cr, uid, reversal_date, context=period_ctx)[0] + if not reversal_journal_id: + reversal_journal_id = move.journal_id.id + + reversal_ref = ''.join([x for x in [move_prefix, move.ref] if x]) + reversal_move_id = self.copy(cr, uid, move.id, + default={ + 'date': reversal_date, + 'period_id': reversal_period_id, + 'ref': reversal_ref, + 'journal_id': reversal_journal_id, + 'to_be_reversed': False, + }, + context=context) + + self.write(cr, uid, [move.id], + {'reversal_id': reversal_move_id, + # ensure to_be_reversed is true if ever it was not + 'to_be_reversed': True}, + context=context) + + reversal_move = self.browse(cr, uid, reversal_move_id, context=context) + for reversal_move_line in reversal_move.line_id: + reversal_ml_name = ' '.join( + [x for x + in [move_line_prefix, reversal_move_line.name] + if x] + ) + move_line_obj.write( + cr, + uid, + [reversal_move_line.id], + {'debit': reversal_move_line.credit, + 'credit': reversal_move_line.debit, + 'amount_currency': reversal_move_line.amount_currency * -1, + 'name': reversal_ml_name}, + context=context, + check=True, + update_check=True) + + self.validate(cr, uid, [reversal_move_id], context=context) + return reversal_move_id + + def create_reversals(self, cr, uid, ids, reversal_date, + reversal_period_id=False, reversal_journal_id=False, + move_prefix=False, move_line_prefix=False, + context=None): + """ + Create the reversal of one or multiple moves + + :param reversal_date: when the reversal must be input + :param reversal_period_id: facultative period to write on the move + (use the period of the date if empty + :param reversal_journal_id: facultative journal on which create + the move + :param move_prefix: prefix for the move's name + :param move_line_prefix: prefix for the move line's names + + :return: Returns a list of ids of the created reversal moves + """ + if isinstance(ids, (int, long)): + ids = [ids] + + reversed_move_ids = [] + for src_move in self.browse(cr, uid, ids, context=context): + if src_move.reversal_id: + continue # skip the reversal creation if already done + + reversal_move_id = self._move_reversal( + cr, uid, + src_move, + reversal_date, + reversal_period_id=reversal_period_id, + reversal_journal_id=reversal_journal_id, + move_prefix=move_prefix, + move_line_prefix=move_line_prefix, + context=context + ) + + if reversal_move_id: + reversed_move_ids.append(reversal_move_id) + + return reversed_move_ids diff --git a/account_reversal/wizard/account_move_reverse.py b/account_reversal/wizard/account_move_reverse.py new file mode 100644 index 000000000..8bb3c696e --- /dev/null +++ b/account_reversal/wizard/account_move_reverse.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Account reversal module for OpenERP +# Copyright (C) 2011 Akretion (http://www.akretion.com). All Rights Reserved +# @author Alexis de Lattre +# Copyright (c) 2012-2013 Camptocamp SA (http://www.camptocamp.com) +# @author Guewen Baconnier +# +# +# 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.osv import orm, fields +from openerp.tools.translate import _ + + +class account_move_reversal(orm.TransientModel): + _name = "account.move.reverse" + _description = "Create reversal of account moves" + + _columns = { + 'date': fields.date( + 'Reversal Date', + required=True, + help="Enter the date of the reversal account entries. " + "By default, OpenERP proposes the first day of " + "the next period."), + 'period_id': fields.many2one( + 'account.period', + 'Reversal Period', + help="If empty, take the period of the date."), + 'journal_id': fields.many2one( + 'account.journal', + 'Reversal Journal', + help='If empty, uses the journal of the journal entry ' + 'to be reversed.'), + 'move_prefix': fields.char( + 'Entries Ref. Prefix', + size=32, + help="Prefix that will be added to the 'Ref' of the journal " + "entry to be reversed to create the 'Ref' of the " + "reversal journal entry (no space added after the prefix)."), + 'move_line_prefix': fields.char( + 'Items Name Prefix', + size=32, + help="Prefix that will be added to the name of the journal " + "item to be reversed to create the name of the reversal " + "journal item (a space is added after the prefix)."), + } + + def _next_period_first_date(self, cr, uid, context=None): + if context is None: + context = {} + res = False + period_ctx = context.copy() + period_ctx['account_period_prefer_normal'] = True + period_obj = self.pool.get('account.period') + today_period_id = period_obj.find(cr, uid, context=period_ctx) + if today_period_id: + today_period = period_obj.browse( + cr, uid, today_period_id[0], context=context) + next_period_id = period_obj.next( + cr, uid, today_period, 1, context=context) + if next_period_id: + next_period = period_obj.browse( + cr, uid, next_period_id, context=context) + res = next_period.date_start + return res + + _defaults = { + 'date': _next_period_first_date, + 'move_line_prefix': 'REV -', + } + + def action_reverse(self, cr, uid, ids, context=None): + if context is None: + context = {} + assert 'active_ids' in context, "active_ids missing in context" + + form = self.read(cr, uid, ids, context=context)[0] + + mod_obj = self.pool.get('ir.model.data') + act_obj = self.pool.get('ir.actions.act_window') + move_obj = self.pool.get('account.move') + move_ids = context['active_ids'] + + period_id = form['period_id'][0] if form.get('period_id') else False + journal_id = form['journal_id'][0] if form.get('journal_id') else False + reversed_move_ids = move_obj.create_reversals( + cr, uid, + move_ids, + form['date'], + reversal_period_id=period_id, + reversal_journal_id=journal_id, + move_prefix=form['move_prefix'], + move_line_prefix=form['move_line_prefix'], + context=context) + + __, action_id = mod_obj.get_object_reference( + cr, uid, 'account', 'action_move_journal_line') + action = act_obj.read(cr, uid, [action_id], context=context)[0] + action['domain'] = unicode([('id', 'in', reversed_move_ids)]) + action['name'] = _('Reversal Entries') + action['context'] = unicode({'search_default_to_be_reversed': 0}) + return action