From ccdc3a1e622e60c327024fabf29409ced69fe349 Mon Sep 17 00:00:00 2001 From: "Benjamin Willig (ACSONE)" Date: Mon, 2 Oct 2017 17:54:04 +0200 Subject: [PATCH] [MIG] account_move_batch_validate: Migration to 10.0 (#510) --- account_move_batch_validate/README.rst | 11 +- account_move_batch_validate/__init__.py | 26 +-- account_move_batch_validate/__manifest__.py | 42 +---- account_move_batch_validate/account.py | 166 ------------------ account_move_batch_validate/account_view.xml | 28 --- .../models/__init__.py | 5 + .../models/account_move.py | 101 +++++++++++ .../test/batch_validate.yml | 61 ------- .../test/batch_validate_then_delete_move.yml | 64 ------- .../test/batch_validate_then_unmark.yml | 87 --------- account_move_batch_validate/tests/__init__.py | 1 + .../tests/test_account_move_batch_validate.py | 163 +++++++++++++++++ .../views/account_move.xml | 33 ++++ .../wizard/__init__.py | 25 +-- .../wizard/account_move_validate.py | 62 +++++++ .../wizard/account_move_validate.xml | 24 +++ .../wizard/move_marker.py | 127 -------------- .../wizard/move_marker_view.xml | 22 --- 18 files changed, 409 insertions(+), 639 deletions(-) delete mode 100644 account_move_batch_validate/account.py delete mode 100644 account_move_batch_validate/account_view.xml create mode 100644 account_move_batch_validate/models/__init__.py create mode 100644 account_move_batch_validate/models/account_move.py delete mode 100644 account_move_batch_validate/test/batch_validate.yml delete mode 100644 account_move_batch_validate/test/batch_validate_then_delete_move.yml delete mode 100644 account_move_batch_validate/test/batch_validate_then_unmark.yml create mode 100644 account_move_batch_validate/tests/__init__.py create mode 100644 account_move_batch_validate/tests/test_account_move_batch_validate.py create mode 100644 account_move_batch_validate/views/account_move.xml create mode 100644 account_move_batch_validate/wizard/account_move_validate.py create mode 100644 account_move_batch_validate/wizard/account_move_validate.xml delete mode 100644 account_move_batch_validate/wizard/move_marker.py delete mode 100644 account_move_batch_validate/wizard/move_marker_view.xml diff --git a/account_move_batch_validate/README.rst b/account_move_batch_validate/README.rst index d3d37d6ae..bddbfe990 100644 --- a/account_move_batch_validate/README.rst +++ b/account_move_batch_validate/README.rst @@ -5,20 +5,14 @@ Account Move Batch Validate =========================== This module provides a wizard to post many Journal Entries in batch. it -uses the queue system introduced by the OpenERP Connector to handle a +uses the queue system introduced by the Odoo Queue job module to handle a big quantity of moves in batch. -The module account_default_draft_move introduces a workflow where the -Journal Entries are always entered in OpenERP in draft state, and the -posting happens later, for example at the end of the period. The core -account module provides a wizard to post all the moves in the period, -but that is problematic when there are many moves. - The posting of a move takes some time, and doing that synchronously, in one transaction is problematic. In this module, we leverage the power of the queue system of the -OpenERP Connector, that can be very well used without other concepts +Odoo queue job module, that can be very well used without other concepts like Backends and Bindings. This approach provides many advantages, similar to the ones we get @@ -56,6 +50,7 @@ Contributors * Rudolf Schnapka * Stéphane Bidoul (ACSONE) * Adrien Peiffer (ACSONE) +* Benjamin Willig (ACSONE) Maintainer ---------- diff --git a/account_move_batch_validate/__init__.py b/account_move_batch_validate/__init__.py index df1a591bd..85bba0cb6 100644 --- a/account_move_batch_validate/__init__.py +++ b/account_move_batch_validate/__init__.py @@ -1,24 +1,6 @@ # -*- coding: utf-8 -*- -############################################################################### -# # -# Author: Leonardo Pistone -# Copyright 2014 Camptocamp SA -# # -# 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 . # -# # -############################################################################### -"""Account Move Batch Validate.""" +# Copyright 2014 Camptocamp SA, 2017 ACSONE +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from . import account # noqa -from . import wizard # noqa +from . import models +from . import wizard diff --git a/account_move_batch_validate/__manifest__.py b/account_move_batch_validate/__manifest__.py index 4f1d28fbb..579701a84 100644 --- a/account_move_batch_validate/__manifest__.py +++ b/account_move_batch_validate/__manifest__.py @@ -1,45 +1,21 @@ # -*- coding: utf-8 -*- -############################################################################### -# # -# Author: Leonardo Pistone -# Copyright 2014 Camptocamp SA -# # -# 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 . # -# # -############################################################################### +# Copyright 2014 Camptocamp SA, 2017 ACSONE +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). { 'name': "Account Move Batch Validate", - 'version': '8.0.0.2.0', - 'author': "Camptocamp,Odoo Community Association (OCA)", + 'version': '10.0.1.0.0', + 'author': "Camptocamp, Odoo Community Association (OCA)", + 'website': 'http://www.camptocamp.com', 'category': 'Finance', 'complexity': 'normal', 'depends': [ 'account', - 'account_default_draft_move', - 'connector', + 'queue_job', ], - 'website': 'http://www.camptocamp.com', 'data': [ - 'account_view.xml', - 'wizard/move_marker_view.xml', + 'views/account_move.xml', + 'wizard/account_move_validate.xml', ], - 'test': [ - 'test/batch_validate.yml', - 'test/batch_validate_then_unmark.yml', - 'test/batch_validate_then_delete_move.yml', - ], - 'installable': False, - 'images': [], + 'installable': True, 'license': 'AGPL-3', } diff --git a/account_move_batch_validate/account.py b/account_move_batch_validate/account.py deleted file mode 100644 index b081e17b2..000000000 --- a/account_move_batch_validate/account.py +++ /dev/null @@ -1,166 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################### -# # -# Author: Leonardo Pistone -# Copyright 2014 Camptocamp SA -# # -# 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 . # -# # -############################################################################### -"""Accounting customisation for delayed posting.""" - -import logging - -from openerp.osv import fields, orm -from openerp.tools.translate import _ - -_logger = logging.getLogger(__name__) - -try: - from openerp.addons.connector.queue.job import job - from openerp.addons.connector.session import ConnectorSession - from openerp.addons.connector.queue.job import OpenERPJobStorage -except ImportError: - _logger.debug('Can not `import connector`.') - import functools - - def empty_decorator_factory(*argv, **kwargs): - return functools.partial - job = empty_decorator_factory - -# do a massive write on account moves BLOCK_SIZE at a time -BLOCK_SIZE = 1000 - - -class account_move(orm.Model): - - """We modify the account move to allow delayed posting.""" - - _name = 'account.move' - _inherit = 'account.move' - - _columns = { - 'to_post': fields.boolean( - 'Posting Requested', - readonly=True, - help='Check this box to mark the move for batch posting' - ), - 'post_job_uuid': fields.char( - 'UUID of the Job to approve this move' - ), - } - - def _delay_post_marked(self, cr, uid, eta=None, context=None): - """Create a job for every move marked for posting. - - If some moves already have a job, they are skipped. - - """ - - if context is None: - context = {} - - session = ConnectorSession(cr, uid, context=context) - - move_ids = self.search(cr, uid, [ - ('to_post', '=', True), - ('post_job_uuid', '=', False), - ('state', '=', 'draft'), - ], context=context) - name = self._name - - # maybe not creating too many dictionaries will make us a bit faster - values = {'post_job_uuid': None} - _logger.info( - u'{0} jobs for posting moves have been created.'.format( - len(move_ids) - ) - ) - - for move_id in move_ids: - job_uuid = validate_one_move.delay(session, name, move_id, - eta=eta) - values['post_job_uuid'] = job_uuid - self.write(cr, uid, [move_id], values) - cr.commit() - - def _cancel_jobs(self, cr, uid, context=None): - """Find moves where the mark has been removed and cancel the jobs. - - For the moves that are posted already it's too late: we skip them. - - """ - - if context is None: - context = {} - - session = ConnectorSession(cr, uid, context=context) - storage = OpenERPJobStorage(session) - - move_ids = self.search(cr, uid, [ - ('to_post', '=', False), - ('post_job_uuid', '!=', False), - ('state', '=', 'draft'), - ], context=context) - - for move in self.browse(cr, uid, move_ids, context=context): - job_rec = storage.load(move.post_job_uuid) - if job_rec.state in (u'pending', u'enqueued'): - job_rec.set_done(result=_( - u'Task set to Done because the user unmarked the move' - )) - storage.store(job_rec) - - def mark_for_posting(self, cr, uid, move_ids, eta=None, context=None): - """Mark a list of moves for delayed posting, and enqueue the jobs.""" - if context is None: - context = {} - # For massive amounts of moves, this becomes necessary to avoid - # MemoryError's - - _logger.info( - u'{0} moves marked for posting.'.format(len(move_ids)) - ) - - for start in xrange(0, len(move_ids), BLOCK_SIZE): - self.write( - cr, - uid, - move_ids[start:start + BLOCK_SIZE], - {'to_post': True}, - context=context) - # users like to see the flag sooner rather than later - cr.commit() - self._delay_post_marked(cr, uid, eta=eta, context=context) - - def unmark_for_posting(self, cr, uid, move_ids, context=None): - """Unmark moves for delayed posting, and cancel the jobs.""" - if context is None: - context = {} - self.write(cr, uid, move_ids, {'to_post': False}, context=context) - self._cancel_jobs(cr, uid, context=context) - - -@job(default_channel='root.account_move_batch_validate') -def validate_one_move(session, model_name, move_id): - """Validate a move, and leave the job reference in place.""" - move_pool = session.pool['account.move'] - if move_pool.exists(session.cr, session.uid, [move_id]): - move_pool.button_validate( - session.cr, - session.uid, - [move_id] - ) - else: - return _(u'Nothing to do because the record has been deleted') diff --git a/account_move_batch_validate/account_view.xml b/account_move_batch_validate/account_view.xml deleted file mode 100644 index 1f5a60af9..000000000 --- a/account_move_batch_validate/account_view.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - view.move.to_post.tree - account.move - - - - - - - - - - view.move.to_post.form - account.move - - - - - - - - - - diff --git a/account_move_batch_validate/models/__init__.py b/account_move_batch_validate/models/__init__.py new file mode 100644 index 000000000..3418a723b --- /dev/null +++ b/account_move_batch_validate/models/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Copyright 2014 Camptocamp SA, 2017 ACSONE +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import account_move diff --git a/account_move_batch_validate/models/account_move.py b/account_move_batch_validate/models/account_move.py new file mode 100644 index 000000000..8cb988bca --- /dev/null +++ b/account_move_batch_validate/models/account_move.py @@ -0,0 +1,101 @@ +# -*- coding: utf-8 -*- +# Copyright 2014 Camptocamp SA, 2017 ACSONE +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import logging + +from odoo import api, fields, models, _ +from odoo.addons.queue_job.job import job, Job + + +_logger = logging.getLogger(__name__) + +BLOCK_SIZE = 1000 + + +class AccountMove(models.Model): + + _inherit = 'account.move' + + to_post = fields.Boolean( + string="Posting requested", readonly=True, + help="Check this box to mark the move for batch posting") + post_job_uuid = fields.Char(string="UUID of the Job to approve this move") + + @job(default_channel='root.account_move_batch_validate') + def validate_one_move(self, move_id): + move = self.browse(move_id) + if move.exists(): + move.post() + else: + return _(u"Nothing to do because the record has been deleted") + + @api.model + def _delay_post_marked(self, eta=None): + """ + Create a job for every move marked for posting. + If some moves already have a job, they are skipped. + """ + AccountMoveObj = self.env[self._name] + + moves = self.search([ + ('to_post', '=', True), + ('post_job_uuid', '=', False), + ('state', '=', 'draft'), + ]) + + # maybe not creating too many dictionaries will make us a bit faster + values = {'post_job_uuid': None} + _logger.info( + "%s jobs for posting moves have been created.", len(moves)) + + for move in moves: + new_job = AccountMoveObj.with_delay(eta=eta).validate_one_move( + move.id) + values['post_job_uuid'] = new_job.uuid + move.write(values) + self.env.cr.commit() # pylint:disable=invalid-commit + + @api.model + def _cancel_post_jobs(self): + """ + Find moves where the mark has been removed and cancel the jobs. + For the moves that are posted already it's too late: we skip them. + """ + moves = self.search([ + ('to_post', '=', False), + ('post_job_uuid', '!=', False), + ('state', '=', 'draft'), + ]) + + for move in moves: + job_rec = Job.load(self.env, move.post_job_uuid) + if job_rec.state in ('pending', 'enqueued'): + job_rec.set_done( + result=_("Task set to Done because the " + "user unmarked the move.")) + job_rec.store() + + @api.multi + def mark_for_posting(self, eta=None): + """ + Mark a list of moves for delayed posting, and enqueue the jobs. + For massive amounts of moves, this becomes necessary to avoid + MemoryError's + """ + moves_count = len(self) + _logger.info("%s moves marked for posting.", moves_count) + + values = {'to_post': True} + + for index in xrange(0, moves_count, BLOCK_SIZE): + moves = self[index:index + BLOCK_SIZE] + moves.write(values) + # users like to see the flag sooner rather than later + self.env.cr.commit() # pylint:disable=invalid-commit + self._delay_post_marked(eta=eta) + + @api.multi + def unmark_for_posting(self): + self.write({'to_post': False}) + self._cancel_post_jobs() diff --git a/account_move_batch_validate/test/batch_validate.yml b/account_move_batch_validate/test/batch_validate.yml deleted file mode 100644 index 898d0dce5..000000000 --- a/account_move_batch_validate/test/batch_validate.yml +++ /dev/null @@ -1,61 +0,0 @@ -- - I create a move -- - !record {model: account.move, id: move1}: - journal_id: account.sales_journal - line_id: - - name: Receivable line - account_id: account.a_recv - debit: 1000.0 - - name: Sales line - account_id: account.a_sale - credit: 1000.0 -- - I check that the move is still draft -- - !assert {model: account.move, id: move1}: - - state == 'draft' -- - I create a wizard -- - !record {model: validate.account.move, id: wiz_marker1}: - action: mark -- - I set the period and the journal on the wizard -- - !python {model: validate.account.move}: | - move = self.pool['account.move'].browse(cr, uid, ref('move1'), - context=context) - journal_ids = [(6, 0, [move.journal_id.id])] - period_ids = [(6, 0, [move.period_id.id])] - vals = {'journal_ids': journal_ids, - 'period_ids': period_ids, - } - self.write(cr, uid, ref('wiz_marker1'), vals, context=context) -- - I run the wizard -- - !python {model: validate.account.move}: | - context['automated_test_execute_now'] = True - self.validate_move( - cr, uid, [ref('wiz_marker1')], context=context - ) -- - I read the UUID from the move, I dequeue the job and run it -- - !python {model: account.move}: | - from openerp.addons.connector.queue.job import OpenERPJobStorage - from openerp.addons.connector.session import ConnectorSession - - move = self.browse(cr, uid, ref('move1'), context=context) - uuid = move.post_job_uuid - session = ConnectorSession(cr, uid, context=context) - storage = OpenERPJobStorage(session) - - myjob = storage.load(uuid) - myjob.perform(session) -- - I check that the move is now approved -- - !assert {model: account.move, id: move1}: - - state == 'posted' diff --git a/account_move_batch_validate/test/batch_validate_then_delete_move.yml b/account_move_batch_validate/test/batch_validate_then_delete_move.yml deleted file mode 100644 index 82a50cbcb..000000000 --- a/account_move_batch_validate/test/batch_validate_then_delete_move.yml +++ /dev/null @@ -1,64 +0,0 @@ -- - I create a move -- - !record {model: account.move, id: move3}: - journal_id: account.sales_journal - line_id: - - name: Receivable line - account_id: account.a_recv - debit: 3000.0 - - name: Sales line - account_id: account.a_sale - credit: 3000.0 -- - I check that the move is still draft -- - !assert {model: account.move, id: move3}: - - state == 'draft' -- - I create a wizard with a long ETA -- - !record {model: validate.account.move, id: wiz_marker4}: - action: mark - eta: 10000 -- - I set the period and the journal on the wizard -- - !python {model: validate.account.move}: | - move = self.pool['account.move'].browse(cr, uid, ref('move3'), - context=context) - journal_ids = [(6, 0, [move.journal_id.id])] - period_ids = [(6, 0, [move.period_id.id])] - vals = {'journal_ids': journal_ids, - 'period_ids': period_ids, - } - self.write(cr, uid, ref('wiz_marker4'), vals, context=context) -- - I run the wizard -- - !python {model: validate.account.move}: | - context['automated_test_execute_now'] = True - self.validate_move( - cr, uid, [ref('wiz_marker4')], context=context - ) -- - I read the UUID from the move, delete the move, then dequeue the job and run it. - It should raise no exceptions. -- - !python {model: account.move}: | - from openerp.addons.connector.queue.job import OpenERPJobStorage - from openerp.addons.connector.session import ConnectorSession - - move = self.browse(cr, uid, ref('move3'), context=context) - uuid = move.post_job_uuid - - assert uuid, 'The Job has not been created.' - self.unlink(cr, uid, ref('move3'), context=context) - - session = ConnectorSession(cr, uid, context=context) - storage = OpenERPJobStorage(session) - - myjob = storage.load(uuid) - myjob.perform(session) - - assert myjob.result == u'Nothing to do because the record has been deleted' diff --git a/account_move_batch_validate/test/batch_validate_then_unmark.yml b/account_move_batch_validate/test/batch_validate_then_unmark.yml deleted file mode 100644 index 2c71c65d7..000000000 --- a/account_move_batch_validate/test/batch_validate_then_unmark.yml +++ /dev/null @@ -1,87 +0,0 @@ -- - I create a move -- - !record {model: account.move, id: move2}: - journal_id: account.sales_journal - line_id: - - name: Receivable line - account_id: account.a_recv - debit: 2000.0 - - name: Sales line - account_id: account.a_sale - credit: 2000.0 -- - I check that the move is still draft -- - !assert {model: account.move, id: move2}: - - state == 'draft' -- - I create a wizard with a long ETA -- - !record {model: validate.account.move, id: wiz_marker2}: - action: mark - eta: 10000 -- - I set the period and the journal on the wizard -- - !python {model: validate.account.move}: | - move = self.pool['account.move'].browse(cr, uid, ref('move2'), - context=context) - journal_ids = [(6, 0, [move.journal_id.id])] - period_ids = [(6, 0, [move.period_id.id])] - vals = {'journal_ids': journal_ids, - 'period_ids': period_ids, - } - self.write(cr, uid, ref('wiz_marker2'), vals, context=context) -- - I run the wizard -- - !python {model: validate.account.move}: | - context['automated_test_execute_now'] = True - self.validate_move( - cr, uid, [ref('wiz_marker2')], context=context - ) -- - Now I change my mind and I create a wizard to unmark the moves -- - !record {model: validate.account.move, id: wiz_unmarker3}: - action: unmark -- - I set the period and the journal on the wizard -- - !python {model: validate.account.move}: | - move = self.pool['account.move'].browse(cr, uid, ref('move2'), - context=context) - journal_ids = [(6, 0, [move.journal_id.id])] - period_ids = [(6, 0, [move.period_id.id])] - vals = {'journal_ids': journal_ids, - 'period_ids': period_ids, - } - self.write(cr, uid, ref('wiz_unmarker3'), vals, context=context) -- - I run the wizard -- - !python {model: validate.account.move}: | - self.validate_move( - cr, uid, [ref('wiz_unmarker3')], context=context - ) -- - Now I checked that my job is done, and the move is still draft -- - !python {model: account.move}: | - from openerp.addons.connector.queue.job import OpenERPJobStorage - from openerp.addons.connector.session import ConnectorSession - - session = ConnectorSession(cr, uid, context=context) - storage = OpenERPJobStorage(session) - - move = self.browse(cr, uid, ref('move2'), context=context) - myjob = storage.load(move.post_job_uuid) - assert myjob.state == 'done', 'Job is in state {0}, should be done'.format( - myjob.state - ) -- - I check that the move is still draft -- - !assert {model: account.move, id: move2}: - - state == 'draft' diff --git a/account_move_batch_validate/tests/__init__.py b/account_move_batch_validate/tests/__init__.py new file mode 100644 index 000000000..36e5827a5 --- /dev/null +++ b/account_move_batch_validate/tests/__init__.py @@ -0,0 +1 @@ +from . import test_account_move_batch_validate diff --git a/account_move_batch_validate/tests/test_account_move_batch_validate.py b/account_move_batch_validate/tests/test_account_move_batch_validate.py new file mode 100644 index 000000000..0d1a74a62 --- /dev/null +++ b/account_move_batch_validate/tests/test_account_move_batch_validate.py @@ -0,0 +1,163 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.tests.common import SingleTransactionCase +from odoo.addons.queue_job.job import Job + + +class TestAccountMoveBatchValidate(SingleTransactionCase): + + @classmethod + def setUpClass(self): + super(TestAccountMoveBatchValidate, self).setUpClass() + self.AccountObj = self.env['account.account'] + self.AccountJournalObj = self.env['account.journal'] + self.AccountMoveObj = self.env['account.move'] + self.ValidateAccountMoveObj = self.env['validate.account.move'] + self.QueueJobObj = self.env['queue.job'] + + self.account_type_recv = self.env.ref( + 'account.data_account_type_receivable') + self.account_type_rev = self.env.ref( + 'account.data_account_type_revenue') + + self.account_recv = self.AccountObj.create({ + 'code': 'RECVT', + 'name': "Receivable (test)", + 'reconcile': True, + 'user_type_id': self.account_type_recv.id, + }) + self.account_sale = self.AccountObj.create({ + 'code': 'SALET', + 'name': "Receivable (sale)", + 'reconcile': True, + 'user_type_id': self.account_type_rev.id, + }) + + self.sales_journal = self.AccountJournalObj.create({ + 'name': "Sales journal", + 'code': 'SAJT', + 'type': 'sale', + 'default_credit_account_id': self.account_sale.id, + 'default_debit_account_id': self.account_sale.id, + }) + + def create_account_move(self, amount): + return self.AccountMoveObj.create({ + 'journal_id': self.sales_journal.id, + 'line_ids': [ + (0, 0, { + 'name': "Receivable line", + 'account_id': self.account_recv.id, + 'debit': amount, + }), + (0, 0, { + 'name': "Sales line", + 'account_id': self.account_type_rev.id, + 'credit': amount, + }), + ] + }) + + def create_move_validate_wizard(self, action, eta=None): + return self.ValidateAccountMoveObj.create({ + 'asynchronous': True, + 'action': action, + 'eta': eta or 0, + }) + + def test_01_wizard_asynchronous_post(self): + """ + Create a move and call the validate account move wizard to + post it. + """ + move = self.create_account_move(1000) + + self.assertEquals(move.state, 'draft') + + wizard = self.create_move_validate_wizard('mark') + wizard.with_context({ + 'active_ids': [move.id], + 'automated_test_execute_now': True, + }).validate_move() + + job_uuid = move.post_job_uuid + + self.assertTrue( + move.to_post, msg="Move should be marked as 'to post'.") + + self.assertTrue( + bool(job_uuid), msg="A job should have been assigned to the move.") + + post_job = Job.load(self.env, job_uuid) + post_job.perform() + + self.assertEquals( + move.state, 'posted', msg="Move should be posted.") + + def test_02_delete_move_before_job_run(self): + """ + Create a move and call the validate account move wizard to + post it, and then delete the move. + """ + move = self.create_account_move(3000) + + wizard = self.create_move_validate_wizard('mark', eta=1000) + wizard.with_context({ + 'active_ids': [move.id], + 'automated_test_execute_now': True, + }).validate_move() + + job_uuid = move.post_job_uuid + + self.assertTrue( + bool(job_uuid), msg="The job has not been created.") + + move.unlink() + + post_job = Job.load(self.env, job_uuid) + post_job.perform() + + self.assertEquals( + post_job.result, + u'Nothing to do because the record has been deleted') + + def test_03_mark_and_unmark(self): + """ + Create a move and call the validate account move wizard to + post it. Recall the validate account move wizard to unmark move. + """ + move = self.create_account_move(3000) + + wizard = self.create_move_validate_wizard('mark', eta=1000) + wizard.with_context({ + 'active_ids': [move.id], + 'automated_test_execute_now': True, + }).validate_move() + + mark_job_uuid = move.post_job_uuid + + self.assertTrue(move.to_post) + + wizard = self.create_move_validate_wizard('unmark', eta=1000) + wizard.with_context({ + 'active_ids': [move.id], + 'automated_test_execute_now': True, + }).validate_move() + + self.assertFalse(move.to_post) + + job_uuid = move.post_job_uuid + + self.assertEquals(mark_job_uuid, job_uuid) + + post_job = Job.load(self.env, job_uuid) + + self.assertEquals(post_job.state, 'done', msg="Job should be done") + self.assertEquals( + post_job.result, + "Task set to Done because the user unmarked the move.") + + self.assertEquals( + move.state, 'draft', msg="Move should be in 'draft' state") diff --git a/account_move_batch_validate/views/account_move.xml b/account_move_batch_validate/views/account_move.xml new file mode 100644 index 000000000..c24ea8554 --- /dev/null +++ b/account_move_batch_validate/views/account_move.xml @@ -0,0 +1,33 @@ + + + + + + + account.move.form in (account_move_batch_validate) + account.move + + + + + + + + + + + + account.move.tree in (account_move_batch_validate) + account.move + + + + + + + + + + + diff --git a/account_move_batch_validate/wizard/__init__.py b/account_move_batch_validate/wizard/__init__.py index f13b63cb0..7beaa5641 100644 --- a/account_move_batch_validate/wizard/__init__.py +++ b/account_move_batch_validate/wizard/__init__.py @@ -1,22 +1,5 @@ # -*- coding: utf-8 -*- -############################################################################### -# # -# Author: Leonardo Pistone -# Copyright 2014 Camptocamp SA -# # -# 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 . # -# # -############################################################################### -"""Wizard to mark account moves for batch posting.""" -from . import move_marker # noqa +# Copyright 2014 Camptocamp SA, 2017 ACSONE +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import account_move_validate diff --git a/account_move_batch_validate/wizard/account_move_validate.py b/account_move_batch_validate/wizard/account_move_validate.py new file mode 100644 index 000000000..867d8f008 --- /dev/null +++ b/account_move_batch_validate/wizard/account_move_validate.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +# Copyright 2014 Camptocamp SA, 2017 ACSONE +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models +from odoo.addons.queue_job.job import job + + +class AccountMoveValidate(models.TransientModel): + + _inherit = 'validate.account.move' + + action = fields.Selection( + selection='_get_actions', string="Action", + required=True, default='mark') + eta = fields.Integer(string="Seconds to wait before starting the jobs") + asynchronous = fields.Boolean(string="Use asynchronous validation") + + @api.model + def _get_actions(self): + return [ + ('mark', 'Mark for posting'), + ('unmark', 'Unmark for posting') + ] + + @api.multi + def validate_move(self): + self.ensure_one() + + if not self.asynchronous: + return super(AccountMoveValidate, self).validate_move() + + wizard_data = { + 'move_ids': self.env.context.get('active_ids'), + 'action': self.action, + 'asynchronous': self.asynchronous, + 'eta': self.eta, + } + + if self.env.context.get('automated_test_execute_now'): + return self.process_wizard(wizard_data) + else: + return self.env[self._name].with_delay(priority=5).process_wizard( + wizard_data) + + @job() + def process_wizard(self, wizard_data): + AccountMoveObj = self.env['account.move'] + + move_ids = wizard_data.get('move_ids') + action = wizard_data.get('action') + eta = wizard_data.get('eta') + + moves = AccountMoveObj.search([ + ('id', 'in', move_ids), + ('state', '=', 'draft') + ]) + + if action == 'mark': + moves.mark_for_posting(eta=eta) + elif action == 'unmark': + moves.unmark_for_posting() diff --git a/account_move_batch_validate/wizard/account_move_validate.xml b/account_move_batch_validate/wizard/account_move_validate.xml new file mode 100644 index 000000000..e0f92353a --- /dev/null +++ b/account_move_batch_validate/wizard/account_move_validate.xml @@ -0,0 +1,24 @@ + + + + + + + + validate.account.move.form (in account_move_batch_validate) + validate.account.move + + + + + + + + + + + + + + diff --git a/account_move_batch_validate/wizard/move_marker.py b/account_move_batch_validate/wizard/move_marker.py deleted file mode 100644 index 849cce5e7..000000000 --- a/account_move_batch_validate/wizard/move_marker.py +++ /dev/null @@ -1,127 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################### -# # -# Author: Leonardo Pistone -# Copyright 2014 Camptocamp SA -# # -# 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 . # -# # -############################################################################### -"""Wizards for batch posting.""" - -import logging - -from openerp.osv import fields, orm - -_logger = logging.getLogger(__name__) - -try: - from openerp.addons.connector.session import ConnectorSession - from openerp.addons.connector.queue.job import job -except ImportError: - _logger.debug('Can not `import connector`.') - - def empty_decorator(func): - return func - job = empty_decorator - - -class ValidateAccountMove(orm.TransientModel): - - """Wizard to mark account moves for batch posting.""" - - _inherit = "validate.account.move" - - _columns = { - 'action': fields.selection([('mark', 'Mark for posting'), - ('unmark', 'Unmark for posting')], - "Action", required=True), - 'eta': fields.integer('Seconds to wait before starting the jobs'), - 'asynchronous': fields.boolean('Use asynchronous validation'), - } - - _defaults = { - 'action': 'mark', - 'asynchronous': True, - } - - def validate_move(self, cr, uid, ids, context=None): - """Create a single job that will create one job per move. - - Return action. - - """ - session = ConnectorSession(cr, uid, context=context) - wizard_id = ids[0] - # to find out what _classic_write does, read the documentation. - wizard_data = self.read(cr, uid, wizard_id, context=context, - load='_classic_write') - if not wizard_data.get('asynchronous'): - return super(ValidateAccountMove, self)\ - .validate_move(cr, uid, ids, context=context) - wizard_data.pop('id') - if wizard_data.get('journal_ids'): - journals_ids_vals = [(6, False, - wizard_data.get('journal_ids'))] - wizard_data['journal_ids'] = journals_ids_vals - if wizard_data.get('period_ids'): - periods_ids_vals = [(6, False, - wizard_data.get('period_ids'))] - wizard_data['period_ids'] = periods_ids_vals - - if context.get('automated_test_execute_now'): - process_wizard(session, self._name, wizard_data) - else: - process_wizard.delay(session, self._name, wizard_data) - - return {'type': 'ir.actions.act_window_close'} - - def process_wizard(self, cr, uid, ids, context=None): - """Choose the correct list of moves to mark and then validate.""" - for wiz in self.browse(cr, uid, ids, context=context): - - move_obj = self.pool['account.move'] - - domain = [('state', '=', 'draft'), - ('journal_id', 'in', wiz.journal_ids.ids), - ('period_id', 'in', wiz.period_ids.ids)] - move_ids = move_obj.search(cr, uid, domain, order='date', - context=context) - - if wiz.action == 'mark': - move_obj.mark_for_posting(cr, uid, move_ids, eta=wiz.eta, - context=context) - - elif wiz.action == 'unmark': - move_obj.unmark_for_posting(cr, uid, move_ids, context=context) - - -@job -def process_wizard(session, model_name, wizard_data): - """Create jobs to validate Journal Entries.""" - - wiz_obj = session.pool[model_name] - new_wiz_id = wiz_obj.create( - session.cr, - session.uid, - wizard_data, - session.context - ) - - wiz_obj.process_wizard( - session.cr, - session.uid, - ids=[new_wiz_id], - context=session.context, - ) diff --git a/account_move_batch_validate/wizard/move_marker_view.xml b/account_move_batch_validate/wizard/move_marker_view.xml deleted file mode 100644 index 0e50c67d4..000000000 --- a/account_move_batch_validate/wizard/move_marker_view.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - Post Journal Entries - validate.account.move - - - - - - - - - - - Post Journal Entries - - - -