Merge branch '13.0' into 13.0-test

This commit is contained in:
Cedric Collins
2023-07-27 16:30:39 -05:00
12 changed files with 385 additions and 26 deletions

View File

@@ -23,6 +23,7 @@ the invoice is currently in.
'account',
],
'data': [
'data/server_actions.xml',
'wizard/invoice_change_views.xml',
],
'installable': True,

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record model="ir.actions.server" id="action_invoice_change">
<field name="name">Change Invoice</field>
<field name="model_id" ref="account.model_account_move"/>
<field name="binding_model_id" ref="account.model_account_move" />
<field name="binding_view_types">list</field>
<field name="state">code</field>
<field name="groups_id" eval="[(4,ref('account.group_account_manager'))]"/>
<field name="code">
if records:
action = env['ir.actions.act_window'].for_xml_id('account_invoice_change', 'action_view_account_invoice_change')
action['context'] = {
'active_model': model._name,
'active_ids': records.ids,
}
</field>
</record>
</data>
</odoo>

View File

@@ -1,10 +1,12 @@
from odoo.addons.account.tests.account_test_users import AccountTestUsers
from odoo import fields
from odoo.exceptions import UserError
from odoo.tests import tagged, Form
from odoo.addons.account.tests.account_test_users import AccountTestUsers
class TestInvoiceChange(AccountTestUsers):
def test_invoice_change_basic(self):
class InvoiceChangeCommon(AccountTestUsers):
def setUp(self):
super().setUp()
self.account_invoice_obj = self.env['account.move']
self.payment_term = self.env.ref('account.account_payment_term_advance')
self.journalrec = self.env['account.journal'].search([('type', '=', 'sale')])[0]
@@ -16,7 +18,7 @@ class TestInvoiceChange(AccountTestUsers):
'journal_id': self.journalrec.id,
'partner_id': self.partner3.id,
# account_id=self.account_rec1_id.id,
'line_ids': [(0, 0, {
'invoice_line_ids': [(0, 0, {
'product_id': self.env.ref('product.product_product_5').id,
'quantity': 10.0,
'account_id': self.env['account.account'].search(
@@ -25,6 +27,11 @@ class TestInvoiceChange(AccountTestUsers):
'price_unit': 100.00,
})],
})
@tagged('post_install', '-at_install')
class TestInvoiceChange(InvoiceChangeCommon):
def test_invoice_change_basic(self):
self.assertEqual(self.invoice_basic.state, 'draft')
self.invoice_basic.action_post()
self.assertEqual(self.invoice_basic.state, 'posted')
@@ -49,3 +56,65 @@ class TestInvoiceChange(AccountTestUsers):
self.assertEqual(str(self.invoice_basic.invoice_date), change_invoice_date)
self.assertEqual(self.invoice_basic.invoice_user_id, change_user)
self.assertEqual(str(self.invoice_basic.line_ids[0].date), change_date)
def _create_journal_entry(self):
revenue_account = self.env['account.account'].search([
('company_id', '=', self.main_company.id),
('user_type_id', '=', self.env.ref('account.data_account_type_revenue').id)
], limit=1)
expense_account = self.env['account.account'].search([
('company_id', '=', self.main_company.id),
('user_type_id', '=', self.env.ref('account.data_account_type_expenses').id)
], limit=1)
return self.env['account.move'].create({
'type': 'entry',
'date': '2019-01-01',
'line_ids': [
(0, 0, {
'name': 'line_debit',
'account_id': revenue_account.id,
}),
(0, 0, {
'name': 'line_credit',
'account_id': expense_account.id,
}),
],
})
def test_invoice_change_multiple(self):
journal_entry = self._create_journal_entry()
ctx = {'active_model': 'account.move', 'active_ids': [self.invoice_basic.id, journal_entry.id]}
with self.assertRaises(UserError):
change = Form(self.env['account.invoice.change'].with_context(ctx))
other_invoice = self.invoice_basic.copy()
ctx = {'active_model': 'account.move', 'active_ids': [self.invoice_basic.id, other_invoice.id]}
with self.assertRaises(UserError):
change = Form(self.env['account.invoice.change'].with_context(ctx))
self.invoice_basic.action_post()
other_invoice.action_post()
change = Form(self.env['account.invoice.change'].with_context(ctx))
self.assertFalse(change.date)
self.assertFalse(change.invoice_date)
self.assertFalse(change.invoice_user_id)
change_date = '2018-01-01'
change_invoice_date = '2019-01-01'
change_user = self.env.user
change.set_invoice_user_id = True
change.set_date = True
change.set_invoice_date = True
change.invoice_user_id = change_user
change.date = change_date
change.invoice_date = change_invoice_date
change.save().affect_change()
self.assertEqual(str(self.invoice_basic.date), change_date)
self.assertEqual(str(self.invoice_basic.invoice_date), change_invoice_date)
self.assertEqual(self.invoice_basic.invoice_user_id, change_user)
self.assertEqual(str(self.invoice_basic.line_ids[0].date), change_date)
self.assertEqual(str(other_invoice.date), change_date)
self.assertEqual(str(other_invoice.invoice_date), change_invoice_date)
self.assertEqual(other_invoice.invoice_user_id, change_user)
self.assertEqual(str(other_invoice.line_ids[0].date), change_date)

View File

@@ -6,11 +6,15 @@ class InvoiceChangeWizard(models.TransientModel):
_name = 'account.invoice.change'
_description = 'Invoice Change'
move_id = fields.Many2one('account.move', string='Invoice', readonly=True, required=True)
move_company_id = fields.Many2one('res.company', readonly=True, related='move_id.company_id')
move_ids = fields.Many2many('account.move', string='Invoice', readonly=True, required=True)
is_single_move = fields.Boolean(compute='_compute_move_company_id')
move_company_id = fields.Many2one('res.company', readonly=True, compute='_compute_move_company_id')
invoice_user_id = fields.Many2one('res.users', string='Salesperson')
set_invoice_user_id = fields.Boolean()
date = fields.Date(string='Accounting Date')
set_date = fields.Boolean()
invoice_date = fields.Date(string='Invoice Date')
set_invoice_date = fields.Boolean()
@api.model
def default_get(self, fields):
@@ -28,24 +32,40 @@ class InvoiceChangeWizard(models.TransientModel):
"Programmation error: the expected model for this action is 'account.move'. The provided one is '%d'.") % active_model)
# Checks on received invoice records
invoice = self.env[active_model].browse(active_ids)
if len(invoice) != 1:
raise UserError(_("Invoice Change expects only one invoice."))
rec.update({
'move_id': invoice.id,
'invoice_user_id': invoice.invoice_user_id.id,
'date': invoice.date,
'invoice_date': invoice.invoice_date,
})
invoices = self.env[active_model].browse(active_ids)
if invoices.filtered(lambda i: not i.is_invoice()):
raise UserError(_('Only invoices can be updated.'))
if invoices.filtered(lambda i: i.state in ('draft', 'cancel')):
raise UserError(_('Only posted invoices can be updated.'))
if len(invoices.company_id) > 1:
raise UserError(_('Selected invoices must be in the same company.'))
rec['move_ids'] = [(6, 0, invoices.ids)]
if len(invoices) == 1:
rec.update({
'set_invoice_user_id': True,
'set_date': True,
'set_invoice_date': True,
'invoice_user_id': invoices.invoice_user_id.id,
'date': invoices.date,
'invoice_date': invoices.invoice_date,
})
return rec
@api.depends('move_ids')
def _compute_move_company_id(self):
for wiz in self:
company = wiz.move_ids.company_id
company.ensure_one()
wiz.move_company_id = company
wiz.is_single_move = len(wiz.move_ids) == 1
def _new_move_vals(self):
vals = {}
if self.move_id.invoice_user_id != self.invoice_user_id:
if self.set_invoice_user_id:
vals['invoice_user_id'] = self.invoice_user_id.id
if self.move_id.date != self.date:
if self.set_date:
vals['date'] = self.date
if self.move_id.invoice_date != self.invoice_date:
if self.set_invoice_date:
vals['invoice_date'] = self.invoice_date
return vals
@@ -53,5 +73,5 @@ class InvoiceChangeWizard(models.TransientModel):
self.ensure_one()
vals = self._new_move_vals()
if vals:
self.move_id.write(vals)
self.move_ids.write(vals)
return True

View File

@@ -7,11 +7,15 @@
<form string="Invoice Change">
<group>
<group name="group_left">
<field name="move_id" invisible="1"/>
<field name="move_ids" invisible="1"/>
<field name="is_single_move" invisible="1"/>
<field name="move_company_id" invisible="1"/>
<field name="invoice_user_id"/>
<field name="invoice_date"/>
<field name="date"/>
<field name="set_invoice_user_id" attrs="{'invisible': [('is_single_move', '=', True)]}"/>
<field name="invoice_user_id" attrs="{'readonly': [('set_invoice_user_id', '=', False)]}"/>
<field name="set_invoice_date" attrs="{'invisible': [('is_single_move', '=', True)]}"/>
<field name="invoice_date" attrs="{'readonly': [('set_invoice_date', '=', False)]}"/>
<field name="set_date" attrs="{'invisible': [('is_single_move', '=', True)]}"/>
<field name="date" attrs="{'readonly': [('set_date', '=', False)]}"/>
</group>
<group name="group_right"/>
</group>
@@ -39,8 +43,7 @@
<xpath expr="//field[@name='state']" position="before">
<button name="%(action_view_account_invoice_change)d" string="Change"
type="action" class="btn-secondary"
attrs="{'invisible': ['|', ('state', 'in', ('sale', 'done', 'cancel')), ('type', 'not in', ('out_invoice', 'out_refund'))]}"
context="{'default_move_id': id}"
attrs="{'invisible': ['|', ('state', 'in', ('draft', 'cancel')), ('type', 'not in', ('out_invoice', 'out_refund'))]}"
groups="account.group_account_manager" />
</xpath>
</field>

View File

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

View File

@@ -0,0 +1,23 @@
{
'name': 'Account Invoice Change - Analytic',
'author': 'Hibou Corp. <hello@hibou.io>',
'version': '13.0.1.0.0',
'category': 'Accounting',
'sequence': 95,
'summary': 'Change Analytic Account on Invoice.',
'description': """
Adds fields and functionality to change the analytic account on all invoice lines
and subsequent documents.
""",
'website': 'https://hibou.io/',
'depends': [
'account_invoice_change',
'analytic',
],
'data': [
'wizard/invoice_change_views.xml',
],
'installable': True,
'application': False,
}

View File

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

View File

@@ -0,0 +1,134 @@
from odoo.tests import tagged, Form
from odoo.addons.account_invoice_change.tests.test_invoice_change import InvoiceChangeCommon
@tagged('post_install', '-at_install')
class TestWizard(InvoiceChangeCommon):
def setUp(self):
super().setUp()
self.analytic_account = self.env['account.analytic.account'].create({
'name': 'test account',
})
self.analytic_account2 = self.env['account.analytic.account'].create({
'name': 'test account2',
})
self.analytic_account3 = self.env['account.analytic.account'].create({
'name': 'test account3',
})
self.analytic_tag1 = self.env['account.analytic.tag'].create({
'name': 'Tag 1',
'active_analytic_distribution': True,
'analytic_distribution_ids': [
(0, 0, {'account_id': self.analytic_account.id, 'percentage': 30.0}),
(0, 0, {'account_id': self.analytic_account2.id, 'percentage': 70.0}),
],
})
self.analytic_tag2 = self.env['account.analytic.tag'].create({
'name': 'Tag 2',
'active_analytic_distribution': True,
'analytic_distribution_ids': [
(0, 0, {'account_id': self.analytic_account.id, 'percentage': 60.0}),
(0, 0, {'account_id': self.analytic_account3.id, 'percentage': 40.0}),
],
})
def test_invoice_change_analytic_account(self):
self.assertEqual(self.invoice_basic.state, 'draft')
self.invoice_basic.action_post()
self.assertEqual(self.invoice_basic.state, 'posted')
# Tests Adding an Analytic Account
self.assertFalse(self.invoice_basic.line_ids.mapped('analytic_account_id'))
ctx = {'active_model': 'account.move', 'active_ids': [self.invoice_basic.id]}
change = Form(self.env['account.invoice.change'].with_context(ctx))
change.analytic_account_id = self.analytic_account
change.save().affect_change()
# Do not want to set analytic account on receivable lines
invoice_lines = self.invoice_basic.invoice_line_ids
other_lines = self.invoice_basic.line_ids - invoice_lines
self.assertEqual(invoice_lines.analytic_account_id, self.analytic_account)
self.assertFalse(other_lines.analytic_account_id)
self.assertEqual(invoice_lines.analytic_line_ids.account_id, self.analytic_account)
# Tests Changing Analytic Account
ctx = {'active_model': 'account.move', 'active_ids': [self.invoice_basic.id]}
change = Form(self.env['account.invoice.change'].with_context(ctx))
change.analytic_account_id = self.analytic_account2
change.save().affect_change()
self.assertEqual(invoice_lines.analytic_account_id, self.analytic_account2)
self.assertFalse(other_lines.analytic_account_id)
self.assertEqual(invoice_lines.analytic_line_ids.account_id, self.analytic_account2)
# Tests Removing Analytic Account
ctx = {'active_model': 'account.move', 'active_ids': [self.invoice_basic.id]}
change = Form(self.env['account.invoice.change'].with_context(ctx))
change.analytic_account_id = self.env['account.analytic.account']
change.save().affect_change()
self.assertFalse(invoice_lines.analytic_account_id)
self.assertFalse(other_lines.analytic_account_id)
self.assertFalse(invoice_lines.analytic_line_ids)
def test_invoice_change_analytic_tags(self):
invoice_lines = self.invoice_basic.invoice_line_ids
other_lines = self.invoice_basic.line_ids - invoice_lines
self.assertEqual(self.invoice_basic.state, 'draft')
invoice_lines.analytic_tag_ids = self.analytic_tag1
self.invoice_basic.action_post()
self.assertEqual(self.invoice_basic.state, 'posted')
self.assertEqual(invoice_lines.analytic_line_ids.mapped(lambda l: (l.account_id, l.amount)),
[(self.analytic_account, 300.0),
(self.analytic_account2, 700.0)])
# Tests Adding an Analytic Account Tag
self.assertFalse(self.invoice_basic.line_ids.mapped('analytic_account_id'))
ctx = {'active_model': 'account.move', 'active_ids': [self.invoice_basic.id]}
change = Form(self.env['account.invoice.change'].with_context(ctx))
change.update_tags = 'add'
change.analytic_tag_ids.add(self.analytic_tag2)
change.save().affect_change()
self.assertEqual(invoice_lines.analytic_tag_ids, self.analytic_tag1 | self.analytic_tag2)
self.assertFalse(other_lines.analytic_tag_ids)
self.assertEqual(invoice_lines.analytic_line_ids.mapped(lambda l: (l.account_id, l.amount)),
[(self.analytic_account, 300.0),
(self.analytic_account2, 700.0),
(self.analytic_account, 600.0),
(self.analytic_account3, 400.0)])
# Tests Replacing all Analytic Account Tags
self.assertFalse(self.invoice_basic.line_ids.mapped('analytic_account_id'))
ctx = {'active_model': 'account.move', 'active_ids': [self.invoice_basic.id]}
change = Form(self.env['account.invoice.change'].with_context(ctx))
self.assertEqual(change.analytic_tag_ids[:], self.analytic_tag1 | self.analytic_tag2)
change.update_tags = 'set'
change.analytic_tag_ids.clear()
change.analytic_tag_ids.add(self.analytic_tag2)
change.save().affect_change()
self.assertEqual(invoice_lines.analytic_tag_ids, self.analytic_tag2)
self.assertFalse(other_lines.analytic_tag_ids)
self.assertEqual(invoice_lines.analytic_line_ids.mapped(lambda l: (l.account_id, l.amount)),
[(self.analytic_account, 600.0),
(self.analytic_account3, 400.0)])
def test_invoice_change_tags_multiple(self):
other_invoice = self.invoice_basic.copy()
self.invoice_basic.invoice_line_ids.analytic_account_id = self.analytic_account
self.invoice_basic.action_post()
other_invoice.invoice_line_ids.analytic_account_id = self.analytic_account2
other_invoice.action_post()
ctx = {'active_model': 'account.move', 'active_ids': [self.invoice_basic.id, other_invoice.id]}
change = Form(self.env['account.invoice.change'].with_context(ctx))
change.update_tags = 'set'
change.analytic_tag_ids.add(self.analytic_tag1)
change.save().affect_change()
# Unchanged unless user chooses to set the value
self.assertEqual(self.invoice_basic.invoice_line_ids.analytic_account_id, self.analytic_account)
self.assertEqual(other_invoice.invoice_line_ids.analytic_account_id, self.analytic_account2)
self.assertEqual(self.invoice_basic.invoice_line_ids.analytic_tag_ids, self.analytic_tag1)
self.assertEqual(other_invoice.invoice_line_ids.analytic_tag_ids, self.analytic_tag1)

View File

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

View File

@@ -0,0 +1,60 @@
from odoo import api, fields, models, _
class InvoiceChangeWizard(models.TransientModel):
_inherit = 'account.invoice.change'
analytic_account_id = fields.Many2one('account.analytic.account', string='Analytic Account')
set_analytic_account_id = fields.Boolean()
update_tags = fields.Selection(selection=[
('no', 'Do not update tags'),
('add', 'Add to tags'),
('set', 'Set tags'),
], default='no', string='Update Analytic Tags')
analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Analytic Tags')
def _analytic_account_id(self, invoice):
analytics = invoice.invoice_line_ids.mapped('analytic_account_id')
if len(analytics):
return analytics[0].id
return False
@api.model
def default_get(self, fields):
rec = super(InvoiceChangeWizard, self).default_get(fields)
context = dict(self._context or {})
invoices = self.env['account.move'].browse(context.get('active_ids'))
if len(invoices) == 1:
rec.update({
'set_analytic_account_id': True,
'analytic_account_id': self._analytic_account_id(invoices),
'analytic_tag_ids': [(6, 0, invoices.invoice_line_ids.analytic_tag_ids.ids)],
})
return rec
def affect_change(self):
res = super(InvoiceChangeWizard, self).affect_change()
self._affect_analytic_change()
return res
def _prepare_analytic_values(self):
vals = {}
if self.set_analytic_account_id:
vals['analytic_account_id'] = self.analytic_account_id.id
tag_commands = []
if self.update_tags == 'add':
tag_commands = [(4, tag.id, 0) for tag in self.analytic_tag_ids]
if self.update_tags == 'set':
tag_commands = [(6, 0, self.analytic_tag_ids.ids)]
if tag_commands:
vals['analytic_tag_ids'] = tag_commands
if vals:
vals['analytic_line_ids'] = [(5, 0, 0)]
return vals
def _affect_analytic_change(self):
lines_to_affect = self.move_ids.invoice_line_ids
vals = self._prepare_analytic_values()
if vals:
lines_to_affect.write(vals)
lines_to_affect.create_analytic_lines()

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="account_invoice_change_form_inherit" model="ir.ui.view">
<field name="name">account.invoice.change.form.inherit</field>
<field name="model">account.invoice.change</field>
<field name="inherit_id" ref="account_invoice_change.account_invoice_change_form"/>
<field name="arch" type="xml">
<xpath expr="//group[@name='group_right']" position="inside">
<field name="set_analytic_account_id" attr="('invisible': [('is_single_move', '=', True)]}" groups="analytic.group_analytic_accounting"/>
<field name="analytic_account_id"
domain="['|', ('company_id', '=', False), ('company_id', '=', move_company_id)]"
attrs="{'readonly': [('set_analytic_account_id', '=', False)]}"
groups="analytic.group_analytic_accounting"/>
<field name="update_tags" groups="analytic.group_analytic_tags"/>
<field name="analytic_tag_ids"
domain="['|', ('company_id', '=', False), ('company_id', '=', move_company_id)]"
groups="analytic.group_analytic_tags"
attrs="{'invisible': [('update_tags', '=', 'no')]}"
widget="many2many_tags"/>
</xpath>
</field>
</record>
</odoo>