Allow to change analytic fields with account_invoice_update
This commit is contained in:
committed by
Iryna Vushnevska
parent
59b0a5ac10
commit
4f6dc99319
@@ -1,5 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# © 2017 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
|
# © 2017 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
|
||||||
|
# Copyright 2018 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).
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -21,6 +22,8 @@ This module adds a button *Update Invoice* on Customer and Supplier invoices in
|
|||||||
* Salesman
|
* Salesman
|
||||||
* Notes
|
* Notes
|
||||||
* Description of invoice lines
|
* Description of invoice lines
|
||||||
|
* Analytic account
|
||||||
|
* Analytic tags
|
||||||
|
|
||||||
""",
|
""",
|
||||||
'author': 'Akretion',
|
'author': 'Akretion',
|
||||||
|
|||||||
1
account_invoice_update_wizard/tests/__init__.py
Normal file
1
account_invoice_update_wizard/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import test_account_invoice_update_wizard
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2018 Camptocamp
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
from odoo.tests.common import TransactionCase
|
||||||
|
from odoo.exceptions import UserError
|
||||||
|
|
||||||
|
|
||||||
|
class TestAccountInvoiceUpdateWizard(TransactionCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestAccountInvoiceUpdateWizard, self).setUp()
|
||||||
|
self.customer12 = self.env.ref('base.res_partner_12')
|
||||||
|
self.product16 = self.env.ref('product.product_product_16')
|
||||||
|
self.product24 = self.env.ref('product.product_product_24')
|
||||||
|
uom_unit = self.env.ref('product.product_uom_categ_unit')
|
||||||
|
|
||||||
|
self.invoice1 = self.env['account.invoice'].create({
|
||||||
|
'name': 'Test invoice',
|
||||||
|
'partner_id': self.customer12.id,
|
||||||
|
})
|
||||||
|
self.inv_line1 = self.env['account.invoice.line'].create({
|
||||||
|
'invoice_id': self.invoice1.id,
|
||||||
|
'name': "Line1",
|
||||||
|
'product_id': self.product16.id,
|
||||||
|
'product_uom_id': uom_unit.id,
|
||||||
|
'account_id': self.invoice1.account_id.id,
|
||||||
|
'price_unit': 42.0,
|
||||||
|
})
|
||||||
|
self.inv_line2 = self.env['account.invoice.line'].create({
|
||||||
|
'invoice_id': self.invoice1.id,
|
||||||
|
'name': "Line2",
|
||||||
|
'product_id': self.product24.id,
|
||||||
|
'product_uom_id': uom_unit.id,
|
||||||
|
'account_id': self.invoice1.account_id.id,
|
||||||
|
'price_unit': 1111.1,
|
||||||
|
})
|
||||||
|
|
||||||
|
self.aa1 = self.env.ref('analytic.analytic_partners_camp_to_camp')
|
||||||
|
self.aa2 = self.env.ref('analytic.analytic_nebula')
|
||||||
|
self.atag1 = self.env.ref('analytic.tag_contract')
|
||||||
|
self.atag2 = self.env['account.analytic.tag'].create({
|
||||||
|
'name': u'の',
|
||||||
|
})
|
||||||
|
|
||||||
|
def create_wizard(self):
|
||||||
|
UpdateWizard = self.env['account.invoice.update'].with_context(
|
||||||
|
active_model='account.invoice',
|
||||||
|
active_id=self.invoice1.id)
|
||||||
|
self.wiz = UpdateWizard.create({})
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
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)
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# © 2017 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
|
# © 2017 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
|
||||||
|
# Copyright 2018 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, _
|
||||||
@@ -48,11 +49,15 @@ class AccountInvoiceUpdate(models.TransientModel):
|
|||||||
for m2ofield in self._m2o_fields2update():
|
for m2ofield in self._m2o_fields2update():
|
||||||
res[m2ofield] = invoice[m2ofield].id or False
|
res[m2ofield] = invoice[m2ofield].id or False
|
||||||
for line in invoice.invoice_line_ids:
|
for line in invoice.invoice_line_ids:
|
||||||
|
aa_tags = line.analytic_tag_ids
|
||||||
|
aa_tags = [(6, 0, aa_tags.ids)] if aa_tags else False
|
||||||
res['line_ids'].append([0, 0, {
|
res['line_ids'].append([0, 0, {
|
||||||
'invoice_line_id': line.id,
|
'invoice_line_id': line.id,
|
||||||
'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_tag_ids': aa_tags,
|
||||||
}])
|
}])
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@@ -110,6 +115,57 @@ class AccountInvoiceUpdate(models.TransientModel):
|
|||||||
mvals['ref'] = ref
|
mvals['ref'] = ref
|
||||||
return mvals
|
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 = {}
|
||||||
|
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 = {}
|
||||||
|
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:
|
||||||
|
alvals['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:
|
||||||
|
alvals['tag_ids'] = [(6, None, new_aa_tags.ids)]
|
||||||
|
return alvals
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _update_payment_term_move(self):
|
def _update_payment_term_move(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
@@ -169,15 +225,40 @@ class AccountInvoiceUpdate(models.TransientModel):
|
|||||||
if ivals:
|
if ivals:
|
||||||
updated = True
|
updated = True
|
||||||
inv.write(ivals)
|
inv.write(ivals)
|
||||||
|
if inv.move_id:
|
||||||
|
mvals = self._prepare_move()
|
||||||
|
if mvals:
|
||||||
|
inv.move_id.write(mvals)
|
||||||
|
for ml in inv.move_id.line_ids:
|
||||||
|
if ml.credit == 0.0:
|
||||||
|
continue
|
||||||
|
inv_line = self._get_matching_inv_line(ml)
|
||||||
|
mlvals = self._prepare_move_line(inv_line)
|
||||||
|
if mlvals:
|
||||||
|
updated = True
|
||||||
|
ml.write(mlvals)
|
||||||
|
aalines = ml.analytic_line_ids
|
||||||
|
alvals = self._prepare_analytic_line(inv_line)
|
||||||
|
if aalines and alvals:
|
||||||
|
updated = True
|
||||||
|
if ('account_id' in alvals and
|
||||||
|
alvals['account_id'] is False):
|
||||||
|
former_aa = inv_line.account_analytic_id
|
||||||
|
to_remove_aalines = aalines.filtered(
|
||||||
|
lambda rec: rec.account_id == former_aa)
|
||||||
|
# remove existing analytic line
|
||||||
|
to_remove_aalines.unlink()
|
||||||
|
else:
|
||||||
|
aalines.write(alvals)
|
||||||
|
elif 'account_id' in alvals:
|
||||||
|
# Create analytic lines if analytic account
|
||||||
|
# is added later
|
||||||
|
ml.create_analytic_lines()
|
||||||
for line in self.line_ids:
|
for line in self.line_ids:
|
||||||
ilvals = self._prepare_invoice_line(line)
|
ilvals = self._prepare_invoice_line(line)
|
||||||
if ilvals:
|
if ilvals:
|
||||||
updated = True
|
updated = True
|
||||||
line.invoice_line_id.write(ilvals)
|
line.invoice_line_id.write(ilvals)
|
||||||
if inv.move_id:
|
|
||||||
mvals = self._prepare_move()
|
|
||||||
if mvals:
|
|
||||||
inv.move_id.write(mvals)
|
|
||||||
if updated:
|
if updated:
|
||||||
inv.message_post(_(
|
inv.message_post(_(
|
||||||
'Non-legal fields of invoice updated via the Invoice Update '
|
'Non-legal fields of invoice updated via the Invoice Update '
|
||||||
@@ -199,3 +280,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(
|
||||||
|
'account.analytic.account', string='Analytic Account')
|
||||||
|
analytic_tag_ids = fields.Many2many(
|
||||||
|
'account.analytic.tag', string='Analytic Tags')
|
||||||
|
|||||||
@@ -30,6 +30,8 @@
|
|||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="quantity"/>
|
<field name="quantity"/>
|
||||||
<field name="price_subtotal"/>
|
<field name="price_subtotal"/>
|
||||||
|
<field name="account_analytic_id"/>
|
||||||
|
<field name="analytic_tag_ids" widget="many2many_tags"/>
|
||||||
</tree>
|
</tree>
|
||||||
</field>
|
</field>
|
||||||
</group>
|
</group>
|
||||||
|
|||||||
Reference in New Issue
Block a user