[MIG] account_move_batch_validate: Migration to 10.0 (#510)

This commit is contained in:
Benjamin Willig (ACSONE)
2017-10-02 17:54:04 +02:00
committed by Iryna Vushnevska
parent 10dab41896
commit 7a19f5b33d
18 changed files with 409 additions and 639 deletions

View File

@@ -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 <rs@techno-flex.de>
* Stéphane Bidoul (ACSONE) <stephane.bidoul@acsone.eu>
* Adrien Peiffer (ACSONE) <adrien.peiffer@acsone.eu>
* Benjamin Willig (ACSONE) <benjamin.willig@acsone.eu>
Maintainer
----------

View File

@@ -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 <http://www.gnu.org/licenses/>. #
# #
###############################################################################
"""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

View File

@@ -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 <http://www.gnu.org/licenses/>. #
# #
###############################################################################
# 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',
}

View File

@@ -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 <http://www.gnu.org/licenses/>. #
# #
###############################################################################
"""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')

View File

@@ -1,28 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="view_move_to_post_tree" model="ir.ui.view">
<field name="name">view.move.to_post.tree</field>
<field name="model">account.move</field>
<field name="inherit_id" ref="account.view_move_tree"/>
<field name="arch" type="xml">
<field name="to_check" position="after">
<field name="to_post"/>
</field>
</field>
</record>
<record id="view_move_to_post_form" model="ir.ui.view">
<field name="name">view.move.to_post.form</field>
<field name="model">account.move</field>
<field name="inherit_id" ref="account.view_move_form"/>
<field name="arch" type="xml">
<field name="to_check" position="after">
<field name="to_post"/>
</field>
</field>
</record>
</data>
</openerp>

View File

@@ -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

View File

@@ -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()

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -0,0 +1 @@
from . import test_account_move_batch_validate

View File

@@ -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")

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2014 Camptocamp SA, 2017 ACSONE SA/NV
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<record model="ir.ui.view" id="account_move_form">
<field name="name">account.move.form in (account_move_batch_validate)</field>
<field name="model">account.move</field>
<field name="inherit_id" ref="account.view_move_form"/>
<field name="arch" type="xml">
<field name="ref" position="after">
<field name="to_post"/>
</field>
</field>
</record>
<record model="ir.ui.view" id="account_move_tree">
<field name="name">account.move.tree in (account_move_batch_validate)</field>
<field name="model">account.move</field>
<field name="inherit_id" ref="account.view_move_tree"/>
<field name="arch" type="xml">
<field name="state" position="after">
<field name="to_post"/>
</field>
</field>
</record>
</odoo>

View File

@@ -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 <http://www.gnu.org/licenses/>. #
# #
###############################################################################
"""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

View File

@@ -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()

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2014 Camptocamp SA, 2017 ACSONE SA/NV
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<record model="ir.ui.view" id="validate_account_move_form">
<field name="name">validate.account.move.form (in account_move_batch_validate)</field>
<field name="model">validate.account.move</field>
<field name="inherit_id" ref="account.validate_account_move_view"/>
<field name="arch" type="xml">
<xpath expr="//footer" position="before">
<group>
<field name="asynchronous"/>
<field name="action" attrs="{'invisible': [('asynchronous', '=', False)]}"/>
</group>
</xpath>
</field>
</record>
</odoo>

View File

@@ -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 <http://www.gnu.org/licenses/>. #
# #
###############################################################################
"""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,
)

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="validate_account_move_view" model="ir.ui.view">
<field name="name">Post Journal Entries</field>
<field name="model">validate.account.move</field>
<field name="inherit_id" ref="account.validate_account_move_view" />
<field name="arch" type="xml">
<xpath expr="//field[@name='journal_ids']" position="before">
<field name="asynchronous" />
<field name="action" attrs="{'invisible': [('asynchronous', '=', False)]}"/>
</xpath>
</field>
</record>
<record id="account.menu_validate_account_moves" model="ir.ui.menu">
<field name="name">Post Journal Entries</field>
</record>
</data>
</openerp>