diff --git a/account_invoice_update_wizard/__manifest__.py b/account_invoice_update_wizard/__manifest__.py index dd60e4e..ab3906a 100644 --- a/account_invoice_update_wizard/__manifest__.py +++ b/account_invoice_update_wizard/__manifest__.py @@ -4,7 +4,7 @@ { 'name': 'Account Invoice Update Wizard', - 'version': '14.0.1.0.0', + 'version': '18.0.1.0.0', 'category': 'Accounting & Finance', 'license': 'AGPL-3', 'summary': 'Wizard to update non-legal fields of an open/paid invoice', @@ -18,5 +18,5 @@ 'wizard/account_move_update_view.xml', 'views/account_move.xml', ], - 'installable': False, + 'installable': True, } diff --git a/account_invoice_update_wizard/tests/test_account_move_update_wizard.py b/account_invoice_update_wizard/tests/test_account_move_update_wizard.py index ec6171e..bc08d5d 100644 --- a/account_invoice_update_wizard/tests/test_account_move_update_wizard.py +++ b/account_invoice_update_wizard/tests/test_account_move_update_wizard.py @@ -1,10 +1,10 @@ # Copyright 2018-2022 Camptocamp # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo.tests.common import SavepointCase +from odoo.tests.common import TransactionCase -class TestAccountInvoiceUpdateWizard(SavepointCase): +class TestAccountInvoiceUpdateWizard(TransactionCase): @classmethod def setUpClass(cls): @@ -13,6 +13,17 @@ class TestAccountInvoiceUpdateWizard(SavepointCase): cls.product16 = cls.env.ref('product.product_product_16') uom_unit = cls.env.ref('uom.product_uom_categ_unit') + cls.plan = cls.env['account.analytic.plan'].create({'name': 'Test Plan'}) + cls.analytic_account_1 = cls.env['account.analytic.account'].create({ + 'name': 'analytic 1 test plan', + 'plan_id': cls.plan.id, + 'company_id': False, + }) + cls.analytic_account_2 = cls.env['account.analytic.account'].create({ + 'name': 'analytic 2 test plan', + 'plan_id': cls.plan.id, + 'company_id': False, + }) cls.move1 = cls.env['account.move'].create({ 'name': 'Test invoice', 'partner_id': cls.customer12.id, @@ -30,13 +41,6 @@ class TestAccountInvoiceUpdateWizard(SavepointCase): ], }) - 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']) @@ -54,13 +58,14 @@ class TestAccountInvoiceUpdateWizard(SavepointCase): 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_distribution = {self.analytic_account_1.id: 50, self.analytic_account_2.id: 50} 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) + self.assertEqual(related_ml.analytic_distribution, {str(self.analytic_account_1.id): 50.0, str(self.analytic_account_2.id): 50.0}) + self.assertEqual(len(related_ml.analytic_line_ids), 2) + self.assertEqual(related_ml.analytic_line_ids[0].amount, 21.0) def test_change_analytic_account_line1(self): """ Change analytic account on a move line @@ -70,86 +75,21 @@ class TestAccountInvoiceUpdateWizard(SavepointCase): - 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 + move_line1.analytic_distribution = {self.analytic_account_1.id: 100} 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_distribution = {self.analytic_account_1.id: 50, self.analytic_account_2.id: 50} 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) + self.assertEqual(related_ml.analytic_distribution, {str(self.analytic_account_1.id): 50.0, str(self.analytic_account_2.id): 50.0}) + self.assertEqual(len(related_ml.analytic_line_ids), 2) + self.assertEqual(related_ml.analytic_line_ids[0].amount, 21.0) def test_empty_analytic_account_line1(self): """ Remove analytic account @@ -158,16 +98,16 @@ class TestAccountInvoiceUpdateWizard(SavepointCase): 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 + move_line1.analytic_distribution = {self.analytic_account_1.id: 100} 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 + wiz_line.analytic_distribution = 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_distribution) self.assertFalse(related_ml.analytic_line_ids) diff --git a/account_invoice_update_wizard/views/account_move.xml b/account_invoice_update_wizard/views/account_move.xml index 4e6ae5b..9429cc0 100644 --- a/account_invoice_update_wizard/views/account_move.xml +++ b/account_invoice_update_wizard/views/account_move.xml @@ -11,8 +11,8 @@ diff --git a/account_invoice_update_wizard/wizard/account_move_update.py b/account_invoice_update_wizard/wizard/account_move_update.py index 873829f..e9a14a2 100644 --- a/account_invoice_update_wizard/wizard/account_move_update.py +++ b/account_invoice_update_wizard/wizard/account_move_update.py @@ -2,7 +2,7 @@ # Copyright 2018-2022 Camptocamp # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import models, fields, api, _ +from odoo import Command, models, fields, api from odoo.exceptions import UserError import odoo.addons.decimal_precision as dp @@ -17,7 +17,7 @@ class AccountMoveUpdate(models.TransientModel): move_type = fields.Selection(related='invoice_id.move_type') company_id = fields.Many2one(related='invoice_id.company_id') partner_id = fields.Many2one(related='invoice_id.partner_id') - user_id = fields.Many2one('res.users', string='Salesperson') + invoice_user_id = fields.Many2one('res.users', string='Salesperson') invoice_payment_term_id = fields.Many2one( 'account.payment.term', string='Payment Term') ref = fields.Char(string='Reference') # field label is customized in the view @@ -35,7 +35,7 @@ class AccountMoveUpdate(models.TransientModel): @api.model def _m2o_fields2update(self): - return ['invoice_payment_term_id', 'user_id', 'partner_bank_id'] + return ['invoice_payment_term_id', 'invoice_user_id', 'partner_bank_id'] @api.model def _prepare_default_get(self, invoice): @@ -45,18 +45,14 @@ class AccountMoveUpdate(models.TransientModel): for m2ofield in self._m2o_fields2update(): res[m2ofield] = invoice[m2ofield].id or False 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, { 'invoice_line_id': line.id, 'sequence': line.sequence, 'name': line.name, 'quantity': line.quantity, 'price_subtotal': line.price_subtotal, - 'analytic_account_id': line.analytic_account_id.id, + 'analytic_distribution': line.analytic_distribution, 'currency_id': line.currency_id.id, - 'analytic_tag_ids': aa_tags, - 'display_type': line.display_type, }]) return res @@ -89,15 +85,15 @@ class AccountMoveUpdate(models.TransientModel): @api.model def _line_simple_fields2update(self): - return ["name"] + return ["name", "analytic_distribution"] @api.model def _line_m2o_fields2update(self): - return ["analytic_account_id"] + return [] @api.model def _line_m2m_fields2update(self): - return ["analytic_tag_ids"] + return [] @api.model def _prepare_invoice_line(self, line): @@ -110,30 +106,9 @@ class AccountMoveUpdate(models.TransientModel): vals[field] = line[field].id for field in self._line_m2m_fields2update(): if line[field] != line.invoice_line_id[field]: - vals[field] = [(6, 0, line[field].ids)] + vals[field] = [Command.set(line[field].ids)] return vals - def _prepare_move_line_and_analytic_line(self, inv_line): - mlvals = {} - alvals = {} - inv_line_upd = self.line_ids.filtered( - lambda rec: rec.invoice_line_id == inv_line) - - ini_aa = inv_line.analytic_account_id - new_aa = inv_line_upd.analytic_account_id - - if ini_aa != new_aa: - mlvals['analytic_account_id'] = new_aa.id - 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: - mlvals['analytic_tag_ids'] = [(6, None, new_aa_tags.ids)] - alvals['tag_ids'] = [(6, None, new_aa_tags.ids)] - return mlvals, alvals - def _update_payment_term_move(self): self.ensure_one() inv = self.invoice_id @@ -146,7 +121,7 @@ class AccountMoveUpdate(models.TransientModel): # the reconcile marks to put the new maturity date on the right # lines if inv.payment_id: - raise UserError(_( + raise UserError(self.env._( "This wizard doesn't support the update of payment " "terms on an invoice which is partially or fully " "paid.")) @@ -170,7 +145,7 @@ class AccountMoveUpdate(models.TransientModel): mlines[amount] = [line] for iamount, lines in mlines.items(): if len(lines) != len(new_pterm.get(iamount, [])): - raise UserError(_( + raise UserError(self.env._( "The original payment term '%s' doesn't have the " "same terms (number of terms and/or amount) as the " "new payment term '%s'. You can only switch to a " @@ -190,41 +165,16 @@ class AccountMoveUpdate(models.TransientModel): if ivals: updated = True inv.write(ivals) - if inv: - for ml in inv.line_ids.filtered( - # we are only interested in invoice lines, not tax lines - lambda rec: bool(rec.product_id) - ): - if ml.credit == 0.0: - continue - analytic_account = ml.analytic_account_id - mlvals, alvals = self._prepare_move_line_and_analytic_line(ml) - if mlvals: - updated = True - ml.write(mlvals) - aalines = ml.analytic_line_ids - if aalines and alvals: - updated = True - if ('account_id' in alvals and - alvals['account_id'] is False): - former_aa = analytic_account - 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: ilvals = self._prepare_invoice_line(line) if ilvals: updated = True + # note that updating analytic_distribution will delete/re-create + # the analytic line with inverse method, we do not need additional + # logic about that. line.invoice_line_id.write(ilvals) if updated: - inv.message_post(body=_( + inv.message_post(body=self.env._( 'Non-legal fields of invoice updated via the Invoice Update ' 'wizard.')) # Purge existing PDF @@ -247,15 +197,23 @@ class AccountMoveLineUpdate(models.TransientModel): invoice_line_id = fields.Many2one( 'account.move.line', string='Invoice Line', readonly=True) name = fields.Text(string='Description', required=True) - display_type = fields.Selection([ - ('line_section', "Section"), - ('line_note', "Note")], default=False, help="Technical field for UX purpose.") + display_type = fields.Selection( + related="invoice_line_id.display_type", + help="Technical field for UX purpose.") quantity = fields.Float( string='Quantity', digits='Product Unit of Measure', readonly=True) price_subtotal = fields.Monetary( string='Amount', readonly=True) - analytic_account_id = fields.Many2one( - 'account.analytic.account', string='Analytic Account') - analytic_tag_ids = fields.Many2many( - 'account.analytic.tag', string='Analytic Tags') currency_id = fields.Many2one('res.currency', readonly=True) + analytic_distribution = fields.Json( + string="Analytic", +# compute="_compute_writeoff_analytic_distribution", +# readonly=False, +# store=True, +# precompute=True, + ) + analytic_precision = fields.Integer( + default=lambda self: self.env["decimal.precision"].precision_get( + "Percentage Analytic" + ), + ) diff --git a/account_invoice_update_wizard/wizard/account_move_update_view.xml b/account_invoice_update_wizard/wizard/account_move_update_view.xml index f772cdd..df51010 100644 --- a/account_invoice_update_wizard/wizard/account_move_update_view.xml +++ b/account_invoice_update_wizard/wizard/account_move_update_view.xml @@ -15,28 +15,33 @@ - - - - - + + + + + - - + + - - - - + + + + - - - - - + + + +