[IMP] account_invoice_update_wizard: Migration to 14.0
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
{
|
{
|
||||||
'name': 'Account Invoice Update Wizard',
|
'name': 'Account Invoice Update Wizard',
|
||||||
'version': '12.0.1.0.0',
|
'version': '14.0.1.0.0',
|
||||||
'category': 'Accounting & Finance',
|
'category': 'Accounting & Finance',
|
||||||
'license': 'AGPL-3',
|
'license': 'AGPL-3',
|
||||||
'summary': 'Wizard to update non-legal fields of an open/paid invoice',
|
'summary': 'Wizard to update non-legal fields of an open/paid invoice',
|
||||||
@@ -14,8 +14,9 @@
|
|||||||
'account',
|
'account',
|
||||||
],
|
],
|
||||||
'data': [
|
'data': [
|
||||||
'wizard/account_invoice_update_view.xml',
|
'security/ir.model.access.csv',
|
||||||
'views/account_invoice.xml',
|
'wizard/account_move_update_view.xml',
|
||||||
],
|
'views/account_move.xml',
|
||||||
'installable': False,
|
],
|
||||||
|
'installable': True,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
from . import account_invoice
|
from . import account_move
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
# Copyright 2019 Camptocamp
|
# Copyright 2019-2022 Camptocamp
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
from odoo import models, fields, api, _
|
from odoo import models
|
||||||
from odoo.exceptions import UserError
|
|
||||||
import odoo.addons.decimal_precision as dp
|
|
||||||
|
|
||||||
|
|
||||||
class AccountInvoice(models.Model):
|
class AccountMove(models.Model):
|
||||||
_inherit = 'account.invoice'
|
_inherit = 'account.move'
|
||||||
|
|
||||||
def prepare_update_wizard(self):
|
def prepare_update_wizard(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
wizard = self.env['account.invoice.update']
|
wizard = self.env['account.move.update']
|
||||||
res = wizard._prepare_default_get(self)
|
res = wizard._prepare_default_get(self)
|
||||||
action = self.env.ref(
|
action = self.env.ref(
|
||||||
'account_invoice_update_wizard.account_invoice_update_action'
|
'account_invoice_update_wizard.account_invoice_update_action'
|
||||||
@@ -19,4 +17,3 @@ class AccountInvoice(models.Model):
|
|||||||
action['name'] = "Update Wizard"
|
action['name'] = "Update Wizard"
|
||||||
action['res_id'] = wizard.create(res).id
|
action['res_id'] = wizard.create(res).id
|
||||||
return action
|
return action
|
||||||
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||||
|
access_account_move_update,account.move.update.user,model_account_move_update,account.group_account_invoice,1,1,1,1
|
||||||
|
access_account_move_line_update,account.move.line.update.user,model_account_move_line_update,account.group_account_invoice,1,1,1,1
|
||||||
|
@@ -1 +1 @@
|
|||||||
from . import test_account_invoice_update_wizard
|
from . import test_account_move_update_wizard
|
||||||
|
|||||||
@@ -1,196 +0,0 @@
|
|||||||
# Copyright 2018-2019 Camptocamp
|
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
||||||
|
|
||||||
from odoo.tests.common import SavepointCase
|
|
||||||
from odoo.exceptions import UserError
|
|
||||||
|
|
||||||
|
|
||||||
class TestAccountInvoiceUpdateWizard(SavepointCase):
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def setUpClass(cls):
|
|
||||||
super().setUpClass()
|
|
||||||
cls.customer12 = cls.env.ref('base.res_partner_12')
|
|
||||||
cls.product16 = cls.env.ref('product.product_product_16')
|
|
||||||
cls.product24 = cls.env.ref('product.product_product_24')
|
|
||||||
uom_unit = cls.env.ref('uom.product_uom_categ_unit')
|
|
||||||
|
|
||||||
cls.invoice1 = cls.env['account.invoice'].create({
|
|
||||||
'name': 'Test invoice',
|
|
||||||
'partner_id': cls.customer12.id,
|
|
||||||
})
|
|
||||||
cls.inv_line1 = cls.env['account.invoice.line'].create({
|
|
||||||
'invoice_id': cls.invoice1.id,
|
|
||||||
'name': "Line1",
|
|
||||||
'product_id': cls.product16.id,
|
|
||||||
'product_uom_id': uom_unit.id,
|
|
||||||
'account_id': cls.invoice1.account_id.id,
|
|
||||||
'price_unit': 42.0,
|
|
||||||
})
|
|
||||||
cls.inv_line2 = cls.env['account.invoice.line'].create({
|
|
||||||
'invoice_id': cls.invoice1.id,
|
|
||||||
'name': "Line2",
|
|
||||||
'product_id': cls.product24.id,
|
|
||||||
'product_uom_id': uom_unit.id,
|
|
||||||
'account_id': cls.invoice1.account_id.id,
|
|
||||||
'price_unit': 1111.1,
|
|
||||||
})
|
|
||||||
|
|
||||||
cls.aa1 = cls.env.ref('analytic.analytic_partners_camp_to_camp')
|
|
||||||
cls.aa2 = cls.env.ref('analytic.analytic_nebula')
|
|
||||||
cls.atag1 = cls.env.ref('analytic.tag_contract')
|
|
||||||
cls.atag2 = cls.env['account.analytic.tag'].create({
|
|
||||||
'name': 'の',
|
|
||||||
})
|
|
||||||
|
|
||||||
def create_wizard(self, invoice):
|
|
||||||
res = self.invoice1.prepare_update_wizard()
|
|
||||||
self.wiz = self.env['account.invoice.update'].browse(res['res_id'])
|
|
||||||
|
|
||||||
def test_add_analytic_account_line1(self):
|
|
||||||
""" Add analytic account on an invoice line
|
|
||||||
after the invoice has been approved.
|
|
||||||
|
|
||||||
This will:
|
|
||||||
- update the move line
|
|
||||||
- create a new analytic line.
|
|
||||||
"""
|
|
||||||
self.invoice1.action_invoice_open()
|
|
||||||
self.create_wizard(self.invoice1)
|
|
||||||
|
|
||||||
wiz_line = self.wiz.line_ids.filtered(
|
|
||||||
lambda rec: rec.invoice_line_id == self.inv_line1)
|
|
||||||
wiz_line.account_analytic_id = self.aa1
|
|
||||||
self.wiz.run()
|
|
||||||
|
|
||||||
related_ml = self.invoice1.move_id.line_ids.filtered(
|
|
||||||
lambda rec: rec.product_id == self.product16)
|
|
||||||
self.assertEqual(related_ml.analytic_account_id, self.aa1)
|
|
||||||
self.assertEqual(related_ml.analytic_line_ids.account_id, self.aa1)
|
|
||||||
|
|
||||||
def test_change_analytic_account_line1(self):
|
|
||||||
""" Change analytic account on an invoice line
|
|
||||||
after the invoice has been approved.
|
|
||||||
|
|
||||||
This will:
|
|
||||||
- update the move line
|
|
||||||
- update the existing analytic line."""
|
|
||||||
self.inv_line1.account_analytic_id = self.aa2
|
|
||||||
|
|
||||||
self.invoice1.action_invoice_open()
|
|
||||||
self.create_wizard(self.invoice1)
|
|
||||||
|
|
||||||
wiz_line = self.wiz.line_ids.filtered(
|
|
||||||
lambda rec: rec.invoice_line_id == self.inv_line1)
|
|
||||||
wiz_line.account_analytic_id = self.aa1
|
|
||||||
self.wiz.run()
|
|
||||||
|
|
||||||
related_ml = self.invoice1.move_id.line_ids.filtered(
|
|
||||||
lambda rec: rec.product_id == self.product16)
|
|
||||||
self.assertEqual(related_ml.analytic_account_id, self.aa1)
|
|
||||||
self.assertEqual(related_ml.analytic_line_ids.account_id, self.aa1)
|
|
||||||
|
|
||||||
def test_error_grouped_move_lines(self):
|
|
||||||
""" Change analytic account on an invoice line
|
|
||||||
after the invoice has been approved where both
|
|
||||||
lines were grouped in the same move line.
|
|
||||||
|
|
||||||
This will raise an error.
|
|
||||||
"""
|
|
||||||
self.invoice1.journal_id.group_invoice_lines = True
|
|
||||||
|
|
||||||
self.inv_line2.product_id = self.product16
|
|
||||||
self.inv_line2.unit_price = 42.0
|
|
||||||
|
|
||||||
self.invoice1.action_invoice_open()
|
|
||||||
self.create_wizard(self.invoice1)
|
|
||||||
|
|
||||||
line1 = self.wiz.line_ids[0]
|
|
||||||
line1.account_analytic_id = self.aa1
|
|
||||||
with self.assertRaises(UserError):
|
|
||||||
self.wiz.run()
|
|
||||||
|
|
||||||
def test_add_analytic_tags_line1(self):
|
|
||||||
""" Add analytic tags on an invoice line
|
|
||||||
after the invoice has been approved.
|
|
||||||
|
|
||||||
This will update move line.
|
|
||||||
"""
|
|
||||||
self.invoice1.action_invoice_open()
|
|
||||||
self.create_wizard(self.invoice1)
|
|
||||||
|
|
||||||
wiz_line = self.wiz.line_ids.filtered(
|
|
||||||
lambda rec: rec.invoice_line_id == self.inv_line1)
|
|
||||||
wiz_line.analytic_tag_ids = self.atag2
|
|
||||||
self.wiz.run()
|
|
||||||
|
|
||||||
related_ml = self.invoice1.move_id.line_ids.filtered(
|
|
||||||
lambda rec: rec.product_id == self.product16)
|
|
||||||
self.assertEqual(related_ml.analytic_tag_ids, self.atag2)
|
|
||||||
self.assertFalse(related_ml.analytic_line_ids)
|
|
||||||
|
|
||||||
def test_change_analytic_tags_line1(self):
|
|
||||||
""" Change analytic tags on an invoice line
|
|
||||||
after the invoice has been approved.
|
|
||||||
|
|
||||||
It will update move line and analytic line
|
|
||||||
"""
|
|
||||||
self.inv_line1.account_analytic_id = self.aa2
|
|
||||||
self.inv_line1.analytic_tag_ids = self.atag1
|
|
||||||
|
|
||||||
self.invoice1.action_invoice_open()
|
|
||||||
self.create_wizard(self.invoice1)
|
|
||||||
|
|
||||||
wiz_line = self.wiz.line_ids.filtered(
|
|
||||||
lambda rec: rec.invoice_line_id == self.inv_line1)
|
|
||||||
wiz_line.analytic_tag_ids = self.atag2
|
|
||||||
self.wiz.run()
|
|
||||||
|
|
||||||
related_ml = self.invoice1.move_id.line_ids.filtered(
|
|
||||||
lambda rec: rec.product_id == self.product16)
|
|
||||||
self.assertEqual(related_ml.analytic_tag_ids, self.atag2)
|
|
||||||
self.assertEqual(related_ml.analytic_line_ids.tag_ids, self.atag2)
|
|
||||||
|
|
||||||
def test_add_analytic_info_line1(self):
|
|
||||||
""" Add analytic account and tags on an invoice line
|
|
||||||
after the invoice has been approved.
|
|
||||||
|
|
||||||
This will:
|
|
||||||
- update move line
|
|
||||||
- create an analytic line
|
|
||||||
"""
|
|
||||||
self.invoice1.action_invoice_open()
|
|
||||||
self.create_wizard(self.invoice1)
|
|
||||||
|
|
||||||
wiz_line = self.wiz.line_ids.filtered(
|
|
||||||
lambda rec: rec.invoice_line_id == self.inv_line1)
|
|
||||||
wiz_line.account_analytic_id = self.aa1
|
|
||||||
wiz_line.analytic_tag_ids = self.atag2
|
|
||||||
self.wiz.run()
|
|
||||||
|
|
||||||
related_ml = self.invoice1.move_id.line_ids.filtered(
|
|
||||||
lambda rec: rec.product_id == self.product16)
|
|
||||||
self.assertEqual(related_ml.analytic_account_id, self.aa1)
|
|
||||||
self.assertEqual(related_ml.analytic_tag_ids, self.atag2)
|
|
||||||
self.assertEqual(related_ml.analytic_line_ids.account_id, self.aa1)
|
|
||||||
self.assertEqual(related_ml.analytic_line_ids.tag_ids, self.atag2)
|
|
||||||
|
|
||||||
def test_empty_analytic_account_line1(self):
|
|
||||||
""" Remove analytic account
|
|
||||||
after the invoice has been approved.
|
|
||||||
|
|
||||||
This will raise an error as it is not implemented.
|
|
||||||
"""
|
|
||||||
self.inv_line1.account_analytic_id = self.aa2
|
|
||||||
|
|
||||||
self.invoice1.action_invoice_open()
|
|
||||||
self.create_wizard(self.invoice1)
|
|
||||||
|
|
||||||
wiz_line = self.wiz.line_ids.filtered(
|
|
||||||
lambda rec: rec.invoice_line_id == self.inv_line1)
|
|
||||||
wiz_line.account_analytic_id = False
|
|
||||||
self.wiz.run()
|
|
||||||
related_ml = self.invoice1.move_id.line_ids.filtered(
|
|
||||||
lambda rec: rec.product_id == self.product16)
|
|
||||||
self.assertFalse(related_ml.analytic_account_id)
|
|
||||||
self.assertFalse(related_ml.analytic_line_ids)
|
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
# Copyright 2018-2022 Camptocamp
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
from odoo.tests.common import SavepointCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestAccountInvoiceUpdateWizard(SavepointCase):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super().setUpClass()
|
||||||
|
cls.customer12 = cls.env.ref('base.res_partner_12')
|
||||||
|
cls.product16 = cls.env.ref('product.product_product_16')
|
||||||
|
uom_unit = cls.env.ref('uom.product_uom_categ_unit')
|
||||||
|
|
||||||
|
cls.move1 = cls.env['account.move'].create({
|
||||||
|
'name': 'Test invoice',
|
||||||
|
'partner_id': cls.customer12.id,
|
||||||
|
'move_type': 'out_invoice',
|
||||||
|
'invoice_line_ids': [
|
||||||
|
[0, None, {
|
||||||
|
'name': 'Line1',
|
||||||
|
'product_id': cls.product16.id,
|
||||||
|
'product_uom_id': uom_unit.id,
|
||||||
|
'quantity': 1,
|
||||||
|
'price_unit': 42.0,
|
||||||
|
'credit': 42.0,
|
||||||
|
'debit': 0
|
||||||
|
}],
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
cls.aa1 = cls.env.ref('analytic.analytic_partners_camp_to_camp')
|
||||||
|
cls.aa2 = cls.env.ref('analytic.analytic_nebula')
|
||||||
|
cls.atag1 = cls.env.ref('analytic.tag_contract')
|
||||||
|
cls.atag2 = cls.env['account.analytic.tag'].create({
|
||||||
|
'name': 'の',
|
||||||
|
})
|
||||||
|
|
||||||
|
def create_wizard(self, move):
|
||||||
|
res = move.prepare_update_wizard()
|
||||||
|
self.wiz = self.env['account.move.update'].browse(res['res_id'])
|
||||||
|
|
||||||
|
def test_add_analytic_account_line1(self):
|
||||||
|
""" Add analytic account on a move line
|
||||||
|
after the move has been approved.
|
||||||
|
|
||||||
|
This will:
|
||||||
|
- update the move line
|
||||||
|
- create a new analytic line.
|
||||||
|
"""
|
||||||
|
self.move1._post()
|
||||||
|
self.create_wizard(self.move1)
|
||||||
|
|
||||||
|
wiz_line = self.wiz.line_ids.filtered(
|
||||||
|
lambda rec: rec.invoice_line_id.product_id.id == self.product16.id)
|
||||||
|
wiz_line.analytic_account_id = self.aa1
|
||||||
|
self.wiz.run()
|
||||||
|
|
||||||
|
related_ml = self.move1.invoice_line_ids.filtered(
|
||||||
|
lambda rec: rec.product_id == self.product16)
|
||||||
|
self.assertEqual(related_ml.analytic_account_id, self.aa1)
|
||||||
|
self.assertEqual(related_ml.analytic_line_ids.account_id, self.aa1)
|
||||||
|
|
||||||
|
def test_change_analytic_account_line1(self):
|
||||||
|
""" Change analytic account on a move line
|
||||||
|
after the move has been approved.
|
||||||
|
|
||||||
|
This will:
|
||||||
|
- update the move line
|
||||||
|
- update the existing analytic line."""
|
||||||
|
move_line1 = self.move1.invoice_line_ids.filtered(lambda rec: rec.product_id == self.product16)
|
||||||
|
move_line1.analytic_account_id = self.aa2
|
||||||
|
|
||||||
|
self.move1._post()
|
||||||
|
self.create_wizard(self.move1)
|
||||||
|
|
||||||
|
wiz_line = self.wiz.line_ids.filtered(
|
||||||
|
lambda rec: rec.invoice_line_id.product_id.id == self.product16.id)
|
||||||
|
wiz_line.analytic_account_id = self.aa1
|
||||||
|
self.wiz.run()
|
||||||
|
|
||||||
|
related_ml = self.move1.invoice_line_ids.filtered(
|
||||||
|
lambda rec: rec.product_id == self.product16)
|
||||||
|
self.assertEqual(related_ml.analytic_account_id, self.aa1)
|
||||||
|
self.assertEqual(related_ml.analytic_line_ids.account_id, self.aa1)
|
||||||
|
|
||||||
|
def test_add_analytic_tags_line1(self):
|
||||||
|
""" Add analytic tags on a move line
|
||||||
|
after the move has been approved.
|
||||||
|
|
||||||
|
This will update move line.
|
||||||
|
"""
|
||||||
|
self.move1._post()
|
||||||
|
self.create_wizard(self.move1)
|
||||||
|
|
||||||
|
wiz_line = self.wiz.line_ids.filtered(
|
||||||
|
lambda rec: rec.invoice_line_id.product_id.id == self.product16.id)
|
||||||
|
wiz_line.analytic_tag_ids = self.atag2
|
||||||
|
self.wiz.run()
|
||||||
|
|
||||||
|
related_ml = self.move1.invoice_line_ids.filtered(
|
||||||
|
lambda rec: rec.product_id == self.product16)
|
||||||
|
self.assertEqual(related_ml.analytic_tag_ids, self.atag2)
|
||||||
|
self.assertFalse(related_ml.analytic_line_ids)
|
||||||
|
|
||||||
|
def test_change_analytic_tags_line1(self):
|
||||||
|
""" Change analytic tags on a move line
|
||||||
|
after the move has been approved.
|
||||||
|
|
||||||
|
It will update move line and analytic line
|
||||||
|
"""
|
||||||
|
move_line1 = self.move1.invoice_line_ids.filtered(lambda rec: rec.product_id == self.product16)
|
||||||
|
move_line1.analytic_account_id = self.aa2
|
||||||
|
move_line1.analytic_tag_ids = self.atag1
|
||||||
|
|
||||||
|
self.move1._post()
|
||||||
|
self.create_wizard(self.move1)
|
||||||
|
|
||||||
|
wiz_line = self.wiz.line_ids.filtered(
|
||||||
|
lambda rec: rec.invoice_line_id.product_id.id == self.product16.id)
|
||||||
|
wiz_line.analytic_tag_ids = self.atag2
|
||||||
|
self.wiz.run()
|
||||||
|
|
||||||
|
related_ml = self.move1.invoice_line_ids.filtered(
|
||||||
|
lambda rec: rec.product_id == self.product16)
|
||||||
|
self.assertEqual(related_ml.analytic_tag_ids, self.atag2)
|
||||||
|
self.assertEqual(related_ml.analytic_line_ids.tag_ids, self.atag2)
|
||||||
|
|
||||||
|
def test_add_analytic_info_line1(self):
|
||||||
|
""" Add analytic account and tags on a move line
|
||||||
|
after the move has been approved.
|
||||||
|
|
||||||
|
This will:
|
||||||
|
- update move line
|
||||||
|
- create an analytic line
|
||||||
|
"""
|
||||||
|
self.move1._post()
|
||||||
|
self.create_wizard(self.move1)
|
||||||
|
|
||||||
|
wiz_line = self.wiz.line_ids.filtered(
|
||||||
|
lambda rec: rec.invoice_line_id.product_id.id == self.product16.id)
|
||||||
|
wiz_line.analytic_account_id = self.aa1
|
||||||
|
wiz_line.analytic_tag_ids = self.atag2
|
||||||
|
self.wiz.run()
|
||||||
|
|
||||||
|
related_ml = self.move1.invoice_line_ids.filtered(
|
||||||
|
lambda rec: rec.product_id == self.product16)
|
||||||
|
self.assertEqual(related_ml.analytic_account_id, self.aa1)
|
||||||
|
self.assertEqual(related_ml.analytic_tag_ids, self.atag2)
|
||||||
|
self.assertEqual(related_ml.analytic_line_ids.account_id, self.aa1)
|
||||||
|
self.assertEqual(related_ml.analytic_line_ids.tag_ids, self.atag2)
|
||||||
|
|
||||||
|
def test_empty_analytic_account_line1(self):
|
||||||
|
""" Remove analytic account
|
||||||
|
after the move has been approved.
|
||||||
|
|
||||||
|
This will raise an error as it is not implemented.
|
||||||
|
"""
|
||||||
|
move_line1 = self.move1.invoice_line_ids.filtered(lambda rec: rec.product_id == self.product16)
|
||||||
|
move_line1.analytic_account_id = self.aa2
|
||||||
|
|
||||||
|
self.move1._post()
|
||||||
|
self.create_wizard(self.move1)
|
||||||
|
|
||||||
|
wiz_line = self.wiz.line_ids.filtered(
|
||||||
|
lambda rec: rec.invoice_line_id.product_id.id == self.product16.id)
|
||||||
|
wiz_line.analytic_account_id = False
|
||||||
|
self.wiz.run()
|
||||||
|
related_ml = self.move1.invoice_line_ids.filtered(
|
||||||
|
lambda rec: rec.product_id == self.product16)
|
||||||
|
self.assertFalse(related_ml.analytic_account_id)
|
||||||
|
self.assertFalse(related_ml.analytic_line_ids)
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!--
|
|
||||||
Copyright 2017 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
|
|
||||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
||||||
-->
|
|
||||||
|
|
||||||
<odoo>
|
|
||||||
|
|
||||||
<record id="invoice_supplier_form" model="ir.ui.view">
|
|
||||||
<field name="model">account.invoice</field>
|
|
||||||
<field name="inherit_id" ref="account.invoice_supplier_form"/>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<button name="action_invoice_draft" position="before">
|
|
||||||
<button name="prepare_update_wizard" type="object" string="Update Invoice" states="open,paid" groups="account.group_account_invoice"/>
|
|
||||||
</button>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record id="invoice_form" model="ir.ui.view">
|
|
||||||
<field name="model">account.invoice</field>
|
|
||||||
<field name="inherit_id" ref="account.invoice_form"/>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<button name="action_invoice_draft" position="before">
|
|
||||||
<button name="prepare_update_wizard" type="object" string="Update Invoice" states="open,paid" groups="account.group_account_invoice"/>
|
|
||||||
</button>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
</odoo>
|
|
||||||
19
account_invoice_update_wizard/views/account_move.xml
Normal file
19
account_invoice_update_wizard/views/account_move.xml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2017 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
|
||||||
|
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
-->
|
||||||
|
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="view_move_form_inherit" model="ir.ui.view">
|
||||||
|
<field name="model">account.move</field>
|
||||||
|
<field name="inherit_id" ref="account.view_move_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<button name="button_draft" position="before">
|
||||||
|
<button name="prepare_update_wizard" type="object" string="Update Invoice" states="posted" groups="account.group_account_invoice"/>
|
||||||
|
</button>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
@@ -1 +1 @@
|
|||||||
from . import account_invoice_update
|
from . import account_move_update
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Copyright 2017 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
|
# Copyright 2017 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
|
||||||
# Copyright 2018-2019 Camptocamp
|
# Copyright 2018-2022 Camptocamp
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
from odoo import models, fields, api, _
|
from odoo import models, fields, api, _
|
||||||
@@ -7,38 +7,37 @@ from odoo.exceptions import UserError
|
|||||||
import odoo.addons.decimal_precision as dp
|
import odoo.addons.decimal_precision as dp
|
||||||
|
|
||||||
|
|
||||||
class AccountInvoiceUpdate(models.TransientModel):
|
class AccountMoveUpdate(models.TransientModel):
|
||||||
_name = 'account.invoice.update'
|
_name = 'account.move.update'
|
||||||
_description = 'Wizard to update non-legal fields of invoice'
|
_description = 'Wizard to update non-legal fields of invoice'
|
||||||
|
|
||||||
invoice_id = fields.Many2one(
|
invoice_id = fields.Many2one(
|
||||||
'account.invoice', string='Invoice', required=True,
|
'account.move', string='Invoice', required=True,
|
||||||
readonly=True)
|
readonly=True)
|
||||||
type = fields.Selection(related='invoice_id.type', readonly=True)
|
type = fields.Selection(related='invoice_id.move_type', readonly=True)
|
||||||
company_id = fields.Many2one(
|
company_id = fields.Many2one(
|
||||||
related='invoice_id.company_id', readonly=True)
|
related='invoice_id.company_id', readonly=True)
|
||||||
partner_id = fields.Many2one(
|
partner_id = fields.Many2one(
|
||||||
related='invoice_id.partner_id', readonly=True)
|
related='invoice_id.partner_id', readonly=True)
|
||||||
user_id = fields.Many2one('res.users', string='Salesperson')
|
user_id = fields.Many2one('res.users', string='Salesperson')
|
||||||
payment_term_id = fields.Many2one(
|
invoice_payment_term_id = fields.Many2one(
|
||||||
'account.payment.term', string='Payment Term')
|
'account.payment.term', string='Payment Term')
|
||||||
reference = fields.Char(string='Invoice Reference')
|
ref = fields.Char(string='Invoice Reference')
|
||||||
name = fields.Char(string='Reference/Description')
|
name = fields.Char(string='Reference/Description')
|
||||||
origin = fields.Char(string='Source Document')
|
invoice_origin = fields.Char(string='Source Document')
|
||||||
comment = fields.Text('Additional Information')
|
|
||||||
partner_bank_id = fields.Many2one(
|
partner_bank_id = fields.Many2one(
|
||||||
'res.partner.bank', string='Bank Account')
|
'res.partner.bank', string='Bank Account')
|
||||||
line_ids = fields.One2many(
|
line_ids = fields.One2many(
|
||||||
'account.invoice.line.update', 'parent_id', string='Invoice Lines')
|
'account.move.line.update', 'parent_id', string='Invoice Lines')
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _simple_fields2update(self):
|
def _simple_fields2update(self):
|
||||||
'''List boolean, date, datetime, char, text fields'''
|
'''List boolean, date, datetime, char, text fields'''
|
||||||
return ['reference', 'name', 'origin', 'comment']
|
return ['ref', 'name', 'invoice_origin']
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _m2o_fields2update(self):
|
def _m2o_fields2update(self):
|
||||||
return ['payment_term_id', 'user_id', 'partner_bank_id']
|
return ['invoice_payment_term_id', 'user_id', 'partner_bank_id']
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _prepare_default_get(self, invoice):
|
def _prepare_default_get(self, invoice):
|
||||||
@@ -55,7 +54,7 @@ class AccountInvoiceUpdate(models.TransientModel):
|
|||||||
'name': line.name,
|
'name': line.name,
|
||||||
'quantity': line.quantity,
|
'quantity': line.quantity,
|
||||||
'price_subtotal': line.price_subtotal,
|
'price_subtotal': line.price_subtotal,
|
||||||
'account_analytic_id': line.account_analytic_id.id,
|
'analytic_account_id': line.analytic_account_id.id,
|
||||||
'analytic_tag_ids': aa_tags,
|
'analytic_tag_ids': aa_tags,
|
||||||
'display_type': line.display_type,
|
'display_type': line.display_type,
|
||||||
}])
|
}])
|
||||||
@@ -72,7 +71,6 @@ class AccountInvoiceUpdate(models.TransientModel):
|
|||||||
"[('partner_id', '=', partner_id)]"
|
"[('partner_id', '=', partner_id)]"
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@api.multi
|
|
||||||
def _prepare_invoice(self):
|
def _prepare_invoice(self):
|
||||||
vals = {}
|
vals = {}
|
||||||
inv = self.invoice_id
|
inv = self.invoice_id
|
||||||
@@ -82,8 +80,8 @@ class AccountInvoiceUpdate(models.TransientModel):
|
|||||||
for m2ofield in self._m2o_fields2update():
|
for m2ofield in self._m2o_fields2update():
|
||||||
if self[m2ofield] != inv[m2ofield]:
|
if self[m2ofield] != inv[m2ofield]:
|
||||||
vals[m2ofield] = self[m2ofield].id or False
|
vals[m2ofield] = self[m2ofield].id or False
|
||||||
if 'payment_term_id' in vals:
|
if 'invoice_payment_term_id' in vals:
|
||||||
pterm_list = self.payment_term_id.compute(
|
pterm_list = self.invoice_payment_term_id.compute(
|
||||||
value=1, date_ref=inv.date_invoice)[0]
|
value=1, date_ref=inv.date_invoice)[0]
|
||||||
if pterm_list:
|
if pterm_list:
|
||||||
vals['date_due'] = max(line[0] for line in pterm_list)
|
vals['date_due'] = max(line[0] for line in pterm_list)
|
||||||
@@ -91,15 +89,15 @@ class AccountInvoiceUpdate(models.TransientModel):
|
|||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _line_simple_fields2update(self):
|
def _line_simple_fields2update(self):
|
||||||
return ["name",]
|
return ["name"]
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _line_m2o_fields2update(self):
|
def _line_m2o_fields2update(self):
|
||||||
return ["account_analytic_id",]
|
return ["analytic_account_id"]
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _line_m2m_fields2update(self):
|
def _line_m2m_fields2update(self):
|
||||||
return ["analytic_tag_ids",]
|
return ["analytic_tag_ids"]
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _prepare_invoice_line(self, line):
|
def _prepare_invoice_line(self, line):
|
||||||
@@ -115,87 +113,45 @@ class AccountInvoiceUpdate(models.TransientModel):
|
|||||||
vals[field] = [(6, 0, line[field].ids)]
|
vals[field] = [(6, 0, line[field].ids)]
|
||||||
return vals
|
return vals
|
||||||
|
|
||||||
@api.multi
|
def _prepare_move_line_and_analytic_line(self, inv_line):
|
||||||
def _prepare_move(self):
|
|
||||||
mvals = {}
|
|
||||||
inv = self.invoice_id
|
|
||||||
ini_ref = inv.move_id.ref
|
|
||||||
ref = inv.reference or inv.name
|
|
||||||
if ini_ref != ref:
|
|
||||||
mvals['ref'] = ref
|
|
||||||
return mvals
|
|
||||||
|
|
||||||
@api.multi
|
|
||||||
def _get_matching_inv_line(self, move_line):
|
|
||||||
""" Find matching invoice line by product """
|
|
||||||
# TODO make it accept more case as lines won't
|
|
||||||
# be grouped unless journal.group_invoice_line is True
|
|
||||||
inv_line = self.invoice_id.invoice_line_ids.filtered(
|
|
||||||
lambda rec: rec.product_id == move_line.product_id)
|
|
||||||
if len(inv_line) != 1:
|
|
||||||
raise UserError(
|
|
||||||
"Cannot match a single invoice line to move line %s" %
|
|
||||||
move_line.name)
|
|
||||||
return inv_line
|
|
||||||
|
|
||||||
@api.multi
|
|
||||||
def _prepare_move_line(self, inv_line):
|
|
||||||
mlvals = {}
|
mlvals = {}
|
||||||
inv_line_upd = self.line_ids.filtered(
|
|
||||||
lambda rec: rec.invoice_line_id == inv_line)
|
|
||||||
|
|
||||||
ini_aa = inv_line.account_analytic_id
|
|
||||||
new_aa = inv_line_upd.account_analytic_id
|
|
||||||
|
|
||||||
if ini_aa != new_aa:
|
|
||||||
mlvals['analytic_account_id'] = new_aa.id
|
|
||||||
|
|
||||||
ini_aa_tags = inv_line.analytic_tag_ids
|
|
||||||
new_aa_tags = inv_line_upd.analytic_tag_ids
|
|
||||||
|
|
||||||
if ini_aa_tags != new_aa_tags:
|
|
||||||
mlvals['analytic_tag_ids'] = [(6, None, new_aa_tags.ids)]
|
|
||||||
return mlvals
|
|
||||||
|
|
||||||
@api.multi
|
|
||||||
def _prepare_analytic_line(self, inv_line):
|
|
||||||
alvals = {}
|
alvals = {}
|
||||||
inv_line_upd = self.line_ids.filtered(
|
inv_line_upd = self.line_ids.filtered(
|
||||||
lambda rec: rec.invoice_line_id == inv_line)
|
lambda rec: rec.invoice_line_id == inv_line)
|
||||||
|
|
||||||
ini_aa = inv_line.account_analytic_id
|
ini_aa = inv_line.analytic_account_id
|
||||||
new_aa = inv_line_upd.account_analytic_id
|
new_aa = inv_line_upd.analytic_account_id
|
||||||
|
|
||||||
if ini_aa != new_aa:
|
if ini_aa != new_aa:
|
||||||
|
mlvals['analytic_account_id'] = new_aa.id
|
||||||
alvals['account_id'] = new_aa.id
|
alvals['account_id'] = new_aa.id
|
||||||
|
|
||||||
ini_aa_tags = inv_line.analytic_tag_ids
|
ini_aa_tags = inv_line.analytic_tag_ids
|
||||||
new_aa_tags = inv_line_upd.analytic_tag_ids
|
new_aa_tags = inv_line_upd.analytic_tag_ids
|
||||||
|
|
||||||
if ini_aa_tags != new_aa_tags:
|
if ini_aa_tags != new_aa_tags:
|
||||||
|
mlvals['analytic_tag_ids'] = [(6, None, new_aa_tags.ids)]
|
||||||
alvals['tag_ids'] = [(6, None, new_aa_tags.ids)]
|
alvals['tag_ids'] = [(6, None, new_aa_tags.ids)]
|
||||||
return alvals
|
return mlvals, alvals
|
||||||
|
|
||||||
@api.multi
|
|
||||||
def _update_payment_term_move(self):
|
def _update_payment_term_move(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
inv = self.invoice_id
|
inv = self.invoice_id
|
||||||
if (
|
if (
|
||||||
self.payment_term_id and
|
self.invoice_payment_term_id and
|
||||||
self.payment_term_id != inv.payment_term_id and
|
self.invoice_payment_term_id != inv.invoice_payment_term_id):
|
||||||
inv.move_id):
|
|
||||||
# I don't update pay term when the invoice is partially (or fully)
|
# I don't update pay term when the invoice is partially (or fully)
|
||||||
# paid because if you have a payment term with several lines
|
# paid because if you have a payment term with several lines
|
||||||
# of the same amount, you would also have to take into account
|
# of the same amount, you would also have to take into account
|
||||||
# the reconcile marks to put the new maturity date on the right
|
# the reconcile marks to put the new maturity date on the right
|
||||||
# lines
|
# lines
|
||||||
if inv.payment_ids:
|
if inv.payment_id:
|
||||||
raise UserError(_(
|
raise UserError(_(
|
||||||
"This wizard doesn't support the update of payment "
|
"This wizard doesn't support the update of payment "
|
||||||
"terms on an invoice which is partially or fully "
|
"terms on an invoice which is partially or fully "
|
||||||
"paid."))
|
"paid."))
|
||||||
prec = self.env['decimal.precision'].precision_get('Account')
|
prec = self.env['decimal.precision'].precision_get('Account')
|
||||||
term_res = self.payment_term_id.compute(
|
term_res = self.invoice_payment_term_id.compute(
|
||||||
inv.amount_total, inv.date_invoice)[0]
|
inv.amount_total, inv.date_invoice)[0]
|
||||||
new_pterm = {} # key = int(amount * 100), value = [date1, date2]
|
new_pterm = {} # key = int(amount * 100), value = [date1, date2]
|
||||||
for entry in term_res:
|
for entry in term_res:
|
||||||
@@ -220,11 +176,10 @@ class AccountInvoiceUpdate(models.TransientModel):
|
|||||||
"new payment term '%s'. You can only switch to a "
|
"new payment term '%s'. You can only switch to a "
|
||||||
"payment term that has the same number of terms "
|
"payment term that has the same number of terms "
|
||||||
"with the same amount.") % (
|
"with the same amount.") % (
|
||||||
inv.payment_term_id.name, self.payment_term_id.name))
|
inv.invoice_payment_term_id.name, self.invoice_payment_term_id.name))
|
||||||
for line in lines:
|
for line in lines:
|
||||||
line.date_maturity = new_pterm[iamount].pop()
|
line.date_maturity = new_pterm[iamount].pop()
|
||||||
|
|
||||||
@api.multi
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
inv = self.invoice_id
|
inv = self.invoice_id
|
||||||
@@ -235,28 +190,24 @@ class AccountInvoiceUpdate(models.TransientModel):
|
|||||||
if ivals:
|
if ivals:
|
||||||
updated = True
|
updated = True
|
||||||
inv.write(ivals)
|
inv.write(ivals)
|
||||||
if inv.move_id:
|
if inv:
|
||||||
mvals = self._prepare_move()
|
for ml in inv.line_ids.filtered(
|
||||||
if mvals:
|
|
||||||
inv.move_id.write(mvals)
|
|
||||||
for ml in inv.move_id.line_ids.filtered(
|
|
||||||
# we are only interested in invoice lines, not tax lines
|
# we are only interested in invoice lines, not tax lines
|
||||||
lambda rec: bool(rec.product_id)
|
lambda rec: bool(rec.product_id)
|
||||||
):
|
):
|
||||||
if ml.credit == 0.0:
|
if ml.credit == 0.0:
|
||||||
continue
|
continue
|
||||||
inv_line = self._get_matching_inv_line(ml)
|
analytic_account = ml.analytic_account_id
|
||||||
mlvals = self._prepare_move_line(inv_line)
|
mlvals, alvals = self._prepare_move_line_and_analytic_line(ml)
|
||||||
if mlvals:
|
if mlvals:
|
||||||
updated = True
|
updated = True
|
||||||
ml.write(mlvals)
|
ml.write(mlvals)
|
||||||
aalines = ml.analytic_line_ids
|
aalines = ml.analytic_line_ids
|
||||||
alvals = self._prepare_analytic_line(inv_line)
|
|
||||||
if aalines and alvals:
|
if aalines and alvals:
|
||||||
updated = True
|
updated = True
|
||||||
if ('account_id' in alvals and
|
if ('account_id' in alvals and
|
||||||
alvals['account_id'] is False):
|
alvals['account_id'] is False):
|
||||||
former_aa = inv_line.account_analytic_id
|
former_aa = analytic_account
|
||||||
to_remove_aalines = aalines.filtered(
|
to_remove_aalines = aalines.filtered(
|
||||||
lambda rec: rec.account_id == former_aa)
|
lambda rec: rec.account_id == former_aa)
|
||||||
# remove existing analytic line
|
# remove existing analytic line
|
||||||
@@ -279,14 +230,14 @@ class AccountInvoiceUpdate(models.TransientModel):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class AccountInvoiceLineUpdate(models.TransientModel):
|
class AccountMoveLineUpdate(models.TransientModel):
|
||||||
_name = 'account.invoice.line.update'
|
_name = 'account.move.line.update'
|
||||||
_description = 'Update non-legal fields of invoice lines'
|
_description = 'Update non-legal fields of invoice lines'
|
||||||
|
|
||||||
parent_id = fields.Many2one(
|
parent_id = fields.Many2one(
|
||||||
'account.invoice.update', string='Wizard', ondelete='cascade')
|
'account.move.update', string='Wizard', ondelete='cascade')
|
||||||
invoice_line_id = fields.Many2one(
|
invoice_line_id = fields.Many2one(
|
||||||
'account.invoice.line', string='Invoice Line', readonly=True)
|
'account.move.line', string='Invoice Line', readonly=True)
|
||||||
name = fields.Text(string='Description', required=True)
|
name = fields.Text(string='Description', required=True)
|
||||||
display_type = fields.Selection([
|
display_type = fields.Selection([
|
||||||
('line_section', "Section"),
|
('line_section', "Section"),
|
||||||
@@ -296,7 +247,7 @@ class AccountInvoiceLineUpdate(models.TransientModel):
|
|||||||
readonly=True)
|
readonly=True)
|
||||||
price_subtotal = fields.Float(
|
price_subtotal = fields.Float(
|
||||||
string='Amount', readonly=True, digits=dp.get_precision('Account'))
|
string='Amount', readonly=True, digits=dp.get_precision('Account'))
|
||||||
account_analytic_id = fields.Many2one(
|
analytic_account_id = fields.Many2one(
|
||||||
'account.analytic.account', string='Analytic Account')
|
'account.analytic.account', string='Analytic Account')
|
||||||
analytic_tag_ids = fields.Many2many(
|
analytic_tag_ids = fields.Many2many(
|
||||||
'account.analytic.tag', string='Analytic Tags')
|
'account.analytic.tag', string='Analytic Tags')
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
<odoo>
|
<odoo>
|
||||||
|
|
||||||
<record id="account_invoice_update_form" model="ir.ui.view">
|
<record id="account_invoice_update_form" model="ir.ui.view">
|
||||||
<field name="model">account.invoice.update</field>
|
<field name="model">account.move.update</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<form string="Update Invoice Wizard">
|
<form string="Update Invoice Wizard">
|
||||||
<group name="main">
|
<group name="main">
|
||||||
@@ -15,13 +15,12 @@
|
|||||||
<field name="type" invisible="1"/>
|
<field name="type" invisible="1"/>
|
||||||
<field name="company_id" invisible="1"/>
|
<field name="company_id" invisible="1"/>
|
||||||
<field name="partner_id" invisible="1"/>
|
<field name="partner_id" invisible="1"/>
|
||||||
<field name="reference" attrs="{'invisible': [('type', 'not in', ('in_invoice', 'in_refund'))]}"/>
|
<field name="ref" attrs="{'invisible': [('type', 'not in', ('in_invoice', 'in_refund'))]}"/>
|
||||||
<field name="origin"/>
|
<field name="invoice_origin"/>
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="payment_term_id" widget="selection"/>
|
<field name="invoice_payment_term_id" widget="selection"/>
|
||||||
<field name="partner_bank_id"/>
|
<field name="partner_bank_id"/>
|
||||||
<field name="user_id"/>
|
<field name="user_id"/>
|
||||||
<field name="comment"/>
|
|
||||||
</group>
|
</group>
|
||||||
<group name="lines">
|
<group name="lines">
|
||||||
<field name="line_ids" nolabel="1">
|
<field name="line_ids" nolabel="1">
|
||||||
@@ -31,7 +30,7 @@
|
|||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="quantity" attrs="{'invisible': [('display_type', '!=', False)]}"/>
|
<field name="quantity" attrs="{'invisible': [('display_type', '!=', False)]}"/>
|
||||||
<field name="price_subtotal" attrs="{'invisible': [('display_type', '!=', False)]}"/>
|
<field name="price_subtotal" attrs="{'invisible': [('display_type', '!=', False)]}"/>
|
||||||
<field name="account_analytic_id" attrs="{'invisible': [('display_type', '!=', False)]}" groups="analytic.group_analytic_accounting"/>
|
<field name="analytic_account_id" attrs="{'invisible': [('display_type', '!=', False)]}" groups="analytic.group_analytic_accounting"/>
|
||||||
<field name="analytic_tag_ids" attrs="{'invisible': [('display_type', '!=', False)]}" groups="analytic.group_analytic_accounting" widget="many2many_tags"/>
|
<field name="analytic_tag_ids" attrs="{'invisible': [('display_type', '!=', False)]}" groups="analytic.group_analytic_accounting" widget="many2many_tags"/>
|
||||||
</tree>
|
</tree>
|
||||||
</field>
|
</field>
|
||||||
@@ -46,7 +45,7 @@
|
|||||||
|
|
||||||
<record id="account_invoice_update_action" model="ir.actions.act_window">
|
<record id="account_invoice_update_action" model="ir.actions.act_window">
|
||||||
<field name="name">Invoice Update Wizard</field>
|
<field name="name">Invoice Update Wizard</field>
|
||||||
<field name="res_model">account.invoice.update</field>
|
<field name="res_model">account.move.update</field>
|
||||||
<field name="view_mode">form</field>
|
<field name="view_mode">form</field>
|
||||||
<field name="target">new</field>
|
<field name="target">new</field>
|
||||||
</record>
|
</record>
|
||||||
Reference in New Issue
Block a user