[PATCH] Upstream patch - 03042023

This commit is contained in:
Parthiv Patel
2023-04-03 08:34:08 +00:00
parent e81b71c26d
commit b69a57d4c0
21 changed files with 315 additions and 201 deletions

View File

@@ -30,7 +30,7 @@ class PortalAccount(CustomerPortal):
return self._get_page_view_values(invoice, access_token, values, 'my_invoices_history', False, **kwargs)
def _get_invoices_domain(self):
return [('state', 'not in', ('cancel', 'draft')), ('is_move_sent', '=', True), ('move_type', 'in', ('out_invoice', 'out_refund', 'in_invoice', 'in_refund', 'out_receipt', 'in_receipt'))]
return [('state', 'not in', ('cancel', 'draft')), ('move_type', 'in', ('out_invoice', 'out_refund', 'in_invoice', 'in_refund', 'out_receipt', 'in_receipt'))]
@http.route(['/my/invoices', '/my/invoices/page/<int:page>'], type='http', auth="user", website=True)
def portal_my_invoices(self, page=1, date_begin=None, date_end=None, sortby=None, filterby=None, **kw):

View File

@@ -33,44 +33,55 @@ def preserve_existing_tags_on_taxes(cr, registry, module):
cr.execute("update ir_model_data set noupdate = 't' where id in %s", [tuple(xml_records.ids)])
def update_taxes_from_templates(cr, chart_template_xmlid):
""" This method will try to update taxes based on their template.
Schematically there are three possible execution path:
[do the template xmlid matches one tax xmlid ?]
-NO--> we *create* a new tax based on the template values
-YES-> [are the tax template and the matching tax similar enough (details see `_is_tax_and_template_same`) ?]
-YES-> We *update* the existing tax's tag (and only tags).
-NO--> We *create* a duplicated tax with template value, and related fiscal positions.
This method is mainly used as a local upgrade script.
Returns a list of tuple (template_id, tax_id) of newly created records.
"""
def _create_tax_from_template(company, template, old_tax=None):
"""
Create a new tax from template with template xmlid, if there was already an old tax with that xmlid we
""" Create a new tax from template with template xmlid, if there was already an old tax with that xmlid we
remove the xmlid from it but don't modify anything else.
"""
def _remove_xml_id(xml_id):
module, name = xml_id.split(".", 1)
module, name = xml_id.split('.', 1)
env['ir.model.data'].search([('module', '=', module), ('name', '=', name)]).unlink()
def _avoid_name_conflict():
conflict_tax = env['account.tax'].search([('name', '=', template.name), ('company_id', '=', company.id),
('type_tax_use', '=', template.type_tax_use), ('tax_scope', '=', template.tax_scope)])
if conflict_tax:
conflict_tax.name = "[old] " + conflict_tax.name
conflict_taxes = env['account.tax'].search([
('name', '=', template.name), ('company_id', '=', company.id),
('type_tax_use', '=', template.type_tax_use), ('tax_scope', '=', template.tax_scope)
])
if conflict_taxes:
for index, conflict_taxes in enumerate(conflict_taxes):
conflict_taxes.name = f"[old{index if index > 0 else ''}] {conflict_taxes.name}"
template_vals = template._get_tax_vals_complete(company)
chart_template = env["account.chart.template"].with_context(default_company_id=company.id)
chart_template = env['account.chart.template'].with_context(default_company_id=company.id)
if old_tax:
xml_id = old_tax.get_xml_id().get(old_tax.id)
if xml_id:
_remove_xml_id(xml_id)
_avoid_name_conflict()
chart_template.create_record_with_xmlid(company, template, "account.tax", template_vals)
chart_template.create_record_with_xmlid(company, template, 'account.tax', template_vals)
def _update_tax_from_template(template, tax):
# -> update the tax : we only updates tax tags
""" Update the tax's tags (and only tags!) based on template values. """
tax_rep_lines = tax.invoice_repartition_line_ids + tax.refund_repartition_line_ids
template_rep_lines = template.invoice_repartition_line_ids + template.refund_repartition_line_ids
for tax_line, template_line in zip(tax_rep_lines, template_rep_lines):
tags_to_add = template_line._get_tags_to_add()
tags_to_unlink = tax_line.tag_ids
if tags_to_add != tags_to_unlink:
tax_line.write({"tag_ids": [(6, 0, tags_to_add.ids)]})
tax_line.write({'tag_ids': [(6, 0, tags_to_add.ids)]})
_cleanup_tags(tags_to_unlink)
def _get_template_to_real_xmlid_mapping(company, model):
"""
This function uses ir_model_data to return a mapping between the templates and the data, using their xmlid
""" This function uses ir_model_data to return a mapping between the templates and the data, using their xmlid
:returns: {
account.tax.template.id: account.tax.id
}
@@ -93,8 +104,7 @@ def update_taxes_from_templates(cr, chart_template_xmlid):
return dict(tuples)
def _is_tax_and_template_same(template, tax):
"""
This function compares account.tax and account.tax.template repartition lines.
""" This function compares account.tax and account.tax.template repartition lines.
A tax is considered the same as the template if they have the same:
- amount_type
- amount
@@ -113,9 +123,7 @@ def update_taxes_from_templates(cr, chart_template_xmlid):
)
def _cleanup_tags(tags):
"""
Checks if the tags are still used in taxes or move lines. If not we delete it.
"""
""" Checks if the tags are still used in taxes or move lines. If not we delete it. """
for tag in tags:
tax_using_tag = env['account.tax.repartition.line'].sudo().search([('tag_ids', 'in', tag.id)], limit=1)
aml_using_tag = env['account.move.line'].sudo().search([('tax_tag_ids', 'in', tag.id)], limit=1)
@@ -124,14 +132,14 @@ def update_taxes_from_templates(cr, chart_template_xmlid):
tag.unlink()
def _update_fiscal_positions_from_templates(company, chart_template_id, new_taxes_template):
chart_template = env["account.chart.template"].browse(chart_template_id)
chart_template = env['account.chart.template'].browse(chart_template_id)
positions = env['account.fiscal.position.template'].search([('chart_template_id', '=', chart_template_id)])
tax_template_ref = _get_template_to_real_xmlid_mapping(company, 'account.tax')
fp_template_ref = _get_template_to_real_xmlid_mapping(company, 'account.fiscal.position')
tax_template_vals = []
for position_template in positions:
fp = env["account.fiscal.position"].browse(fp_template_ref.get(position_template.id))
fp = env['account.fiscal.position'].browse(fp_template_ref.get(position_template.id))
if not fp:
continue
for position_tax in position_template.tax_ids:
@@ -147,8 +155,8 @@ def update_taxes_from_templates(cr, chart_template_xmlid):
def _notify_accountant_managers(taxes_to_check):
accountant_manager_group = env.ref("account.group_account_manager")
partner_managers_ids = accountant_manager_group.users.mapped('partner_id')
flectrabot = env.ref('base.partner_root')
partner_managers_ids = accountant_manager_group.users.partner_id.ids
flectrabot_id = env.ref('base.partner_root').id
message_body = _(
"Please check these taxes. They might be outdated. We did not update them. "
"Indeed, they do not exactly match the taxes of the original version of the localization module.<br/>"
@@ -157,15 +165,12 @@ def update_taxes_from_templates(cr, chart_template_xmlid):
for account_tax in taxes_to_check:
message_body += f"<li>{html_escape(account_tax.name)}</li>"
message_body += "</ul>"
for partner_manager in partner_managers_ids:
partner_manager.message_post(
subject=_('Your taxes have been updated !'),
author_id=flectrabot.id,
body=message_body,
message_type='notification',
subtype_xmlid='mail.mt_comment',
partner_ids=partner_manager.ids,
)
env['mail.thread'].message_notify(
subject=_('Your taxes have been updated !'),
author_id=flectrabot_id,
body=message_body,
partner_ids=partner_managers_ids,
)
env = api.Environment(cr, SUPERUSER_ID, {})
chart_template_id = env['ir.model.data'].xmlid_to_res_id(chart_template_xmlid)
@@ -174,9 +179,9 @@ def update_taxes_from_templates(cr, chart_template_xmlid):
new_taxes_template = []
for company in companies:
template_to_tax = _get_template_to_real_xmlid_mapping(company, 'account.tax')
templates = env['account.tax.template'].with_context(active_test=False).search([("chart_template_id", "=", chart_template_id)])
templates = env['account.tax.template'].with_context(active_test=False).search([('chart_template_id', '=', chart_template_id)])
for template in templates:
tax = env["account.tax"].browse(template_to_tax.get(template.id))
tax = env['account.tax'].browse(template_to_tax.get(template.id))
if not tax or not _is_tax_and_template_same(template, tax):
_create_tax_from_template(company, template, old_tax=tax)
if tax:

View File

@@ -243,14 +243,14 @@
<record id="account_invoice_rule_portal" model="ir.rule">
<field name="name">Portal Personal Account Invoices</field>
<field name="model_id" ref="account.model_account_move"/>
<field name="domain_force">[('state', 'not in', ('cancel', 'draft')), ('is_move_sent', '=', True), ('move_type', 'in', ('out_invoice', 'out_refund', 'in_invoice', 'in_refund')), ('message_partner_ids','child_of',[user.commercial_partner_id.id])]</field>
<field name="domain_force">[('state', 'not in', ('cancel', 'draft')), ('move_type', 'in', ('out_invoice', 'out_refund', 'in_invoice', 'in_refund')), ('message_partner_ids','child_of',[user.commercial_partner_id.id])]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
</record>
<record id="account_invoice_line_rule_portal" model="ir.rule">
<field name="name">Portal Invoice Lines</field>
<field name="model_id" ref="account.model_account_move_line"/>
<field name="domain_force">[('parent_state', 'not in', ('cancel', 'draft')), ('move_id.is_move_sent', '=', True), ('move_id.move_type', 'in', ('out_invoice', 'out_refund', 'in_invoice', 'in_refund')), ('move_id.message_partner_ids','child_of',[user.commercial_partner_id.id])]</field>
<field name="domain_force">[('parent_state', 'not in', ('cancel', 'draft')), ('move_id.move_type', 'in', ('out_invoice', 'out_refund', 'in_invoice', 'in_refund')), ('move_id.message_partner_ids','child_of',[user.commercial_partner_id.id])]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
</record>

View File

@@ -1,3 +1,5 @@
import logging
from flectra.addons.account.models.chart_template import update_taxes_from_templates
from flectra.tests import tagged
from flectra.tests.common import SavepointCase
@@ -6,18 +8,65 @@ from flectra.tests.common import SavepointCase
@tagged('post_install', '-at_install')
class TestChartTemplate(SavepointCase):
@classmethod
def setUpClass(cls):
""" Set up a company with the generic chart template, containing two taxes and a fiscal position.
We need to add xml_ids to the templates because they are loaded from their xml_ids
"""
super().setUpClass()
be_country_id = cls.env.ref('base.be').id
cls.company = cls.env['res.company'].create({
'name': 'TestCompany1',
'country_id': be_country_id,
'account_tax_fiscal_country_id': be_country_id,
})
cls.chart_template = cls.env.ref('l10n_generic_coa.configurable_chart_template', raise_if_not_found=False)
if not cls.chart_template:
cls.skipTest(cls, "Accounting Tests skipped because the generic chart of accounts was not found")
cls.fiscal_position_template = cls._create_fiscal_position_template('account.test_fiscal_position_template',
'BE fiscal position test', be_country_id)
cls.tax_template_1 = cls._create_tax_template('account.test_tax_template_1', 'Tax name 1', 1, 'tag_name_1')
cls.tax_template_2 = cls._create_tax_template('account.test_tax_template_2', 'Tax name 2', 2, 'tag_name_2')
cls.fiscal_position_tax_template_1 = cls._create_fiscal_position_tax_template(
cls.fiscal_position_template, 'account.test_fp_tax_template_1', cls.tax_template_1, cls.tax_template_2
)
cls.chart_template.try_loading(company=cls.company)
cls.chart_template_xmlid = cls.chart_template.get_external_id()[cls.chart_template.id]
cls.fiscal_position = cls.env['account.fiscal.position'].search([
('company_id', '=', cls.company.id),
('name', '=', cls.fiscal_position_template.name),
])
@classmethod
def create_tax_template(cls, name, template_name, amount):
# TODO to remove in master
logging.warning("Deprecated method, please use _create_tax_template() instead")
return cls._create_tax_template(template_name, name, amount, tag_name=None)
@classmethod
def _create_tax_template(cls, tax_template_xmlid, name, amount, tag_name=None):
if tag_name:
tag = cls.env['account.account.tag'].create({
'name': tag_name,
'applicability': 'taxes',
'country_id': cls.company.account_tax_fiscal_country_id.id,
})
return cls.env['account.tax.template']._load_records([{
'xml_id': template_name,
'xml_id': tax_template_xmlid,
'values': {
'name': name,
'amount': amount,
'type_tax_use': 'none',
'chart_template_id': cls.chart_template.id,
'invoice_repartition_line_ids': [
(0, 0, {
'factor_percent': 100,
'repartition_type': 'base',
'tag_ids': [(6, 0, tag.ids)] if tag_name else None,
}),
(0, 0, {
'factor_percent': 100,
@@ -28,6 +77,7 @@ class TestChartTemplate(SavepointCase):
(0, 0, {
'factor_percent': 100,
'repartition_type': 'base',
'tag_ids': [(6, 0, tag.ids)] if tag_name else None,
}),
(0, 0, {
'factor_percent': 100,
@@ -38,160 +88,180 @@ class TestChartTemplate(SavepointCase):
}])
@classmethod
def setUpClass(cls):
"""
Setups a company with a custom chart template, containing a tax and a fiscal position.
We need to add xml_ids to the templates because they are loaded from their xml_ids
"""
super().setUpClass()
# Create user.
user = cls.env['res.users'].create({
'name': 'Because I am accountman!',
'login': 'accountman',
'password': 'accountman',
'groups_id': [(6, 0, cls.env.user.groups_id.ids), (4, cls.env.ref('account.group_account_user').id)],
})
user.partner_id.email = 'accountman@test.com'
cls.company_1 = cls.env['res.company'].create({
'name': 'TestCompany1',
'country_id': cls.env.ref('base.be').id,
})
cls.env = cls.env(user=user)
cls.cr = cls.env.cr
user.write({
'company_ids': [(6, 0, cls.company_1.ids)],
'company_id': cls.company_1.id,
})
cls.chart_template = cls.env['account.chart.template']._load_records([{
'xml_id': 'account.test_chart_template',
def _create_fiscal_position_template(cls, fp_template_xmlid, fp_template_name, country_id):
return cls.env['account.fiscal.position.template']._load_records([{
'xml_id': fp_template_xmlid,
'values': {
'name': 'Test Chart Template',
'code_digits': 6,
'currency_id': cls.env.ref('base.EUR').id,
'bank_account_code_prefix': 1000,
'cash_account_code_prefix': 2000,
'transfer_account_code_prefix': 3000,
},
}])
cls.tax_1_template = cls.create_tax_template('Tax 1', 'account.test_tax_1_template', 15)
cls.tax_2_template = cls.create_tax_template('Tax 2', 'account.test_tax_2_template', 0)
cls.fiscal_position_template = cls.env['account.fiscal.position.template']._load_records([{
'xml_id': 'account.test_fiscal_position_template',
'values': {
'name': 'Fiscal Position',
'name': fp_template_name,
'chart_template_id': cls.chart_template.id,
'country_id': cls.env.ref('base.be').id,
'country_id': country_id,
'auto_apply': True,
},
}])
cls.env['account.fiscal.position.tax.template']._load_records([{
'xml_id': 'account.test_fiscal_position_tax_template_1',
@classmethod
def _create_fiscal_position_tax_template(cls, fiscal_position_template, fiscal_position_tax_template_xmlid, tax_template_src, tax_template_dest):
return cls.env['account.fiscal.position.tax.template']._load_records([{
'xml_id': fiscal_position_tax_template_xmlid,
'values': {
'tax_src_id': cls.tax_1_template.id,
'tax_dest_id': cls.tax_2_template.id,
'position_id': cls.fiscal_position_template.id,
'tax_src_id': tax_template_src.id,
'tax_dest_id': tax_template_dest.id,
'position_id': fiscal_position_template.id,
},
}])
cls.chart_template.try_loading(company=cls.company_1)
cls.partner_1 = cls.env['res.partner'].create({
'name': 'Partner 1',
'country_id': cls.env.ref('base.be').id,
})
def test_update_taxes_from_templates(self):
def test_update_taxes_new_template(self):
""" Tests that adding a new tax template and a fiscal position tax template
creates this new tax and fiscal position line when updating
"""
Tests that adding a new tax template and a fiscal position tax template with this new tax template
creates this new tax and fiscal position line when updating
"""
fiscal_position = self.env['account.fiscal.position'].get_fiscal_position(self.partner_1.id)
tax_3_template = self.create_tax_template('Tax 3', 'account.test_tax_3_template', 16)
tax_4_template = self.create_tax_template('Tax 4', 'account.test_tax_4_template', 17)
tax_template_3 = self._create_tax_template('account.test_tax_3_template', 'Tax name 3', 3, 'tag_name_3')
tax_template_4 = self._create_tax_template('account.test_tax_4_template', 'Tax name 4', 4)
self._create_fiscal_position_tax_template(self.fiscal_position_template, 'account.test_fiscal_position_tax_template', tax_template_3, tax_template_4)
update_taxes_from_templates(self.env.cr, self.chart_template_xmlid)
self.env['account.fiscal.position.tax.template']._load_records([
{
'xml_id': 'account.test_fiscal_position_new_tax_src_template',
'values': {
'tax_src_id': tax_3_template.id,
'tax_dest_id': self.tax_1_template.id,
'position_id': self.fiscal_position_template.id,
},
},
{
'xml_id': 'account.test_fiscal_position_new_tax_dest_template',
'values': {
'tax_src_id': self.tax_2_template.id,
'tax_dest_id': tax_4_template.id,
'position_id': self.fiscal_position_template.id,
},
},
taxes = self.env['account.tax'].search([
('company_id', '=', self.company.id),
('name', 'in', [tax_template_3.name, tax_template_4.name]),
])
taxes = self.env['account.tax'].search([('company_id', '=', self.company_1.id)])
# Only the template have been created, so it should not be reflected yet on the company's chart template
self.assertEqual(len(fiscal_position.tax_ids), 1)
self.assertEqual(len(taxes), 2)
chart_template_xml_id = self.chart_template.get_external_id()[self.chart_template.id]
update_taxes_from_templates(self.env.cr, chart_template_xml_id)
taxes = self.env['account.tax'].search([('company_id', '=', self.company_1.id)])
self.assertRecordValues(taxes, [
{'name': 'Tax 1'},
{'name': 'Tax 2'},
{'name': 'Tax 3'},
{'name': 'Tax 4'},
{'name': 'Tax name 3', 'amount': 3},
{'name': 'Tax name 4', 'amount': 4},
])
self.assertEqual(taxes.invoice_repartition_line_ids.tag_ids.name, 'tag_name_3')
self.assertRecordValues(self.fiscal_position.tax_ids.tax_src_id, [
{'name': 'Tax name 1'},
{'name': 'Tax name 3'},
])
self.assertRecordValues(self.fiscal_position.tax_ids.tax_dest_id, [
{'name': 'Tax name 2'},
{'name': 'Tax name 4'},
])
self.assertRecordValues(fiscal_position.tax_ids.tax_src_id, [
{'name': 'Tax 1'},
{'name': 'Tax 3'},
{'name': 'Tax 2'},
])
self.assertRecordValues(fiscal_position.tax_ids.tax_dest_id, [
{'name': 'Tax 2'},
{'name': 'Tax 1'},
{'name': 'Tax 4'},
])
def test_update_taxes_removed_from_templates(self):
def test_update_taxes_existing_template_update(self):
""" When a template is close enough from the corresponding existing tax we want to update
that tax with the template values.
"""
Tests updating after the removal of taxes and fiscal position mapping from the company
self.tax_template_1.invoice_repartition_line_ids.tag_ids.name += " [DUP]"
update_taxes_from_templates(self.env.cr, self.chart_template_xmlid)
tax = self.env['account.tax'].search([
('company_id', '=', self.company.id),
('name', '=', self.tax_template_1.name),
])
# Check that tax was not recreated
self.assertEqual(len(tax), 1)
# Check that tags have been updated
self.assertEqual(tax.invoice_repartition_line_ids.tag_ids.name, self.tax_template_1.invoice_repartition_line_ids.tag_ids.name)
def test_update_taxes_existing_template_recreation(self):
""" When a template is too different from the corresponding existing tax we want to recreate
a new taxes from template.
"""
fiscal_position = self.env['account.fiscal.position'].get_fiscal_position(self.partner_1.id)
fiscal_position.tax_ids.unlink()
self.env['account.tax'].search([('company_id', '=', self.company_1.id)]).unlink()
# We increment the amount so the template gets slightly different from the
# corresponding tax and triggers recreation
old_tax_name = self.tax_template_1.name
old_tax_amount = self.tax_template_1.amount
self.tax_template_1.name = "Tax name 1 modified"
self.tax_template_1.amount += 1
update_taxes_from_templates(self.env.cr, self.chart_template_xmlid)
chart_template_xml_id = self.chart_template.get_external_id()[self.chart_template.id]
update_taxes_from_templates(self.env.cr, chart_template_xml_id)
# Check that old tax has not been changed
old_tax = self.env['account.tax'].search([
('company_id', '=', self.company.id),
('name', '=', old_tax_name),
], limit=1)
self.assertEqual(old_tax[0].amount, old_tax_amount)
# if taxes have been deleted, they will be recreated, and the fiscal position mapping for it too
self.assertEqual(len(self.env['account.tax'].search([('company_id', '=', self.company_1.id)])), 2)
self.assertEqual(len(fiscal_position.tax_ids), 1)
# Check that new tax has been recreated
tax = self.env['account.tax'].search([
('company_id', '=', self.company.id),
('name', '=', self.tax_template_1.name),
], limit=1)
self.assertEqual(tax[0].amount, self.tax_template_1.amount)
fiscal_position.tax_ids.unlink()
update_taxes_from_templates(self.env.cr, chart_template_xml_id)
# if only the fiscal position mapping has been removed, it won't be recreated
self.assertEqual(len(fiscal_position.tax_ids), 0)
def test_update_taxes_remove_fiscal_position_from_tax(self):
""" Tests that when we remove the tax from the fiscal position mapping it is not
recreated after update of taxes.
"""
self.fiscal_position.tax_ids.unlink()
update_taxes_from_templates(self.env.cr, self.chart_template_xmlid)
self.assertEqual(len(self.fiscal_position.tax_ids), 0)
def test_update_taxes_conflict_name(self):
chart_template_xml_id = self.chart_template.get_external_id()[self.chart_template.id]
template_vals = self.tax_1_template._get_tax_vals_complete(self.company_1)
template_vals['amount'] = 20
self.chart_template.create_record_with_xmlid(self.company_1, self.tax_1_template, "account.tax", template_vals)
update_taxes_from_templates(self.env.cr, chart_template_xml_id)
tax_1_old = self.env['account.tax'].search([('company_id', '=', self.company_1.id), ('name', '=', "[old] " + self.tax_1_template.name)])
tax_1_new = self.env['account.tax'].search([('company_id', '=', self.company_1.id), ('name', '=', self.tax_1_template.name)])
self.assertEqual(len(tax_1_old), 1, "Old tax still exists but with a different name.")
self.assertEqual(len(tax_1_new), 1, "New tax have been created with the original name.")
""" When recreating a tax during update a conflict name can occur since
we need to respect unique constraint on (name, company_id, type_tax_use, tax_scope).
To do so, the old tax needs to be prefixed with '[old] '.
"""
# We increment the amount so the template gets slightly different from the
# corresponding tax and triggers recreation
old_amount = self.tax_template_1.amount
self.tax_template_1.amount += 1
update_taxes_from_templates(self.env.cr, self.chart_template_xmlid)
taxes_from_template_1 = self.env['account.tax'].search([
('company_id', '=', self.company.id),
('name', 'like', f"%{self.tax_template_1.name}"),
])
self.assertRecordValues(taxes_from_template_1, [
{'name': f"[old] {self.tax_template_1.name}", 'amount': old_amount},
{'name': f"{self.tax_template_1.name}", 'amount': self.tax_template_1.amount},
])
def test_update_taxes_multi_company(self):
""" In a multi-company environment all companies should be correctly updated."""
company_2 = self.env['res.company'].create({
'name': 'TestCompany2',
'country_id': self.env.ref('base.be').id,
'account_tax_fiscal_country_id': self.env.ref('base.be').id,
})
self.chart_template.try_loading(company=company_2)
# triggers recreation of taxes related to template 1
self.tax_template_1.amount += 1
update_taxes_from_templates(self.env.cr, self.chart_template_xmlid)
taxes_from_template_1 = self.env['account.tax'].search([
('name', 'like', f"%{self.tax_template_1.name}"),
('company_id', 'in', [self.company.id, company_2.id]),
])
# we should have 4 records: 2 companies * (1 original tax + 1 recreated tax)
self.assertEqual(len(taxes_from_template_1), 4)
def test_message_to_accountants(self):
""" When we duplicate a tax because it was too different from the existing one we send
a message to accountant advisors. This message should only be sent to advisors
and not to regular users.
"""
# create 1 normal user, 2 accountants managers
accountant_manager_group = self.env.ref('account.group_account_manager')
advisor_users = self.env['res.users'].create([{
'name': 'AccountAdvisorTest1',
'login': 'aat1',
'password': 'aat1aat1',
'groups_id': [(4, accountant_manager_group.id)],
}, {
'name': 'AccountAdvisorTest2',
'login': 'aat2',
'password': 'aat2aat2',
'groups_id': [(4, accountant_manager_group.id)],
}])
normal_user = self.env['res.users'].create([{
'name': 'AccountUserTest1',
'login': 'aut1',
'password': 'aut1aut1',
'groups_id': [(4, self.env.ref('account.group_account_user').id)],
}])
# create situation where we need to recreate the tax during update to get notification(s) sent
self.tax_template_1.amount += 1
update_taxes_from_templates(self.env.cr, self.chart_template_xmlid)
# accountants received the message
self.assertEqual(self.env['mail.message'].search_count([
('partner_ids', 'in', advisor_users.partner_id.ids),
('body', 'like', f"%{self.tax_template_1.name}%"), # we look for taxes' name that have been sent in the message's body
]), 1)
# normal user didn't
self.assertEqual(self.env['mail.message'].search_count([
('partner_ids', 'in', normal_user.partner_id.ids),
('body', 'like', f"%{self.tax_template_1.name}%"), # we look for taxes' name that have been sent in the message's body
]), 0)

View File

@@ -93,12 +93,15 @@ class CouponProgram(models.Model):
def write(self, vals):
res = super(CouponProgram, self).write(vals)
if not self:
return res
reward_fields = [
'reward_type', 'reward_product_id', 'discount_type', 'discount_percentage',
'discount_apply_on', 'discount_specific_product_ids', 'discount_fixed_amount'
]
if any(field in reward_fields for field in vals):
self.mapped('discount_line_product_id').write({'name': self[0].reward_id.display_name})
for program in self:
program.discount_line_product_id.write({'name': program.reward_id.display_name})
return res
def unlink(self):

View File

@@ -206,7 +206,14 @@ class AccountAnalyticLine(models.Model):
# set timesheet UoM from the AA company (AA implies uom)
if not vals.get('product_uom_id') and all(v in vals for v in ['account_id', 'project_id']): # project_id required to check this is timesheet flow
analytic_account = self.env['account.analytic.account'].sudo().browse(vals['account_id'])
vals['product_uom_id'] = analytic_account.company_id.project_time_mode_id.id
uom_id = analytic_account.company_id.project_time_mode_id.id
if not uom_id:
company_id = vals.get('company_id', False)
if not company_id:
project = self.env['project.project'].browse(vals.get('project_id'))
company_id = project.analytic_account_id.company_id.id or project.company_id.id
uom_id = self.env['res.company'].browse(company_id).project_time_mode_id.id
vals['product_uom_id'] = uom_id
return vals
def _timesheet_postprocess(self, values):

View File

@@ -490,3 +490,12 @@ class TestTimesheet(TestCommonTimesheet):
'unit_amount': 8.0,
})
self.assertEqual(with_user_timesheet.user_id, self.user_employee, 'User Employee is set in timesheet.')
def test_create_timesheet_with_companyless_analytic_account(self):
""" This test ensures that a timesheet can be created on an analytic account whose company_id is set to False"""
self.project_customer.analytic_account_id.company_id = False
timesheet = self.env['account.analytic.line'].with_user(self.user_employee).create(
{'unit_amount': 1.0, 'project_id': self.project_customer.id})
self.assertEqual(timesheet.product_uom_id, self.project_customer.company_id.project_time_mode_id,
"The product_uom_id of the timesheet should be equal to the project's company uom "
"if the project's analytic account has no company_id")

View File

@@ -31,6 +31,15 @@ class DriverController(http.Controller):
if iot_device:
iot_device.data['owner'] = session_id
data = json.loads(data)
# Skip the request if it was already executed (duplicated action calls)
iot_idempotent_id = data.get("iot_idempotent_id")
if iot_idempotent_id:
idempotent_session = iot_device._check_idempotency(iot_idempotent_id, session_id)
if idempotent_session:
_logger.info("Ignored request from %s as iot_idempotent_id %s already received from session %s",
session_id, iot_idempotent_id, idempotent_session)
return False
iot_device.action(data)
return True
return False

View File

@@ -4,6 +4,7 @@
from threading import Thread, Event
from flectra.addons.hw_drivers.main import drivers, iot_devices
from flectra.tools.lru import LRU
class DriverMetaClass(type):
@@ -34,6 +35,9 @@ class Driver(Thread, metaclass=DriverMetaClass):
self.data = {'value': ''}
self._stopped = Event()
# Least Recently Used (LRU) Cache that will store the idempotent keys already seen.
self._iot_idempotent_ids_cache = LRU(500)
@classmethod
def supported(cls, device):
"""
@@ -51,3 +55,18 @@ class Driver(Thread, metaclass=DriverMetaClass):
def disconnect(self):
self._stopped.set()
del iot_devices[self.device_identifier]
def _check_idempotency(self, iot_idempotent_id, session_id):
"""
Some IoT requests for the same action might be received several times.
To avoid duplicating the resulting actions, we check if the action was "recently" executed.
If this is the case, we will simply ignore the action
:return: the `session_id` of the same `iot_idempotent_id` if any. False otherwise,
which means that it is the first time that the IoT box received the request with this ID
"""
cache = self._iot_idempotent_ids_cache
if iot_idempotent_id in cache:
return cache[iot_idempotent_id]
cache[iot_idempotent_id] = session_id
return False

View File

@@ -64,6 +64,7 @@ class ResCompany(models.Model):
if not orders:
msg_alert = (_('There isn\'t any order flagged for data inalterability yet for the company %s. This mechanism only runs for point of sale orders generated after the installation of the module France - Certification CGI 286 I-3 bis. - POS', self.env.company.name))
raise UserError(msg_alert)
previous_hash = u''
corrupted_orders = []

View File

@@ -37,15 +37,8 @@ class AccountEdiFormat(models.Model):
_logger.error('Error while receiving file from SdiCoop: %s', e)
proxy_acks = []
retrigger = False
for id_transaction, fattura in res.items():
# The server has a maximum number of documents it can send at a time
# If that maximum is reached, then we search for more
# by re-triggering the download cron, avoiding the timeout.
current_num, max_num = fattura.get('current_num', 0), fattura.get('max_num', 0)
retrigger = retrigger or current_num == max_num > 0
if self.env['ir.attachment'].search([('name', '=', fattura['filename']), ('res_model', '=', 'account.move')], limit=1):
# name should be unique, the invoice already exists
_logger.info('E-invoice already exist: %s', fattura['filename'])
@@ -88,9 +81,6 @@ class AccountEdiFormat(models.Model):
except AccountEdiProxyError as e:
_logger.error('Error while receiving file from SdiCoop: %s', e)
if retrigger:
self.env.ref('l10n_it_edi.ir_cron_receive_fattura_pa_invoice')._trigger()
# -------------------------------------------------------------------------
# Export
# -------------------------------------------------------------------------

View File

@@ -169,7 +169,7 @@ var PosDB = core.Class.extend({
if (product.description_sale) {
str += '|' + product.description_sale;
}
str = product.id + ':' + str.replace(/:/g,'') + '\n';
str = product.id + ':' + str.replace(/[\n:]/g,'') + '\n';
return str;
},
add_products: function(products){

View File

@@ -480,8 +480,8 @@ form label {
.o_portal_chatter_message_title {
p {
font-size:85%;
color:$o-main-color-muted;
font-size: 85%;
color: $text-muted;
margin: 0px;
}
}
@@ -561,4 +561,3 @@ form label {
}
}
}

View File

@@ -321,7 +321,8 @@ class PurchaseOrderLine(models.Model):
new_date = fields.Datetime.to_datetime(values['date_planned'])
self._update_move_date_deadline(new_date)
lines = self.filtered(lambda l: l.order_id.state == 'purchase')
previous_product_qty = {line.id: line.product_uom_qty for line in lines}
previous_product_uom_qty = {line.id: line.product_uom_qty for line in lines}
previous_product_qty = {line.id: line.product_qty for line in lines}
result = super(PurchaseOrderLine, self).write(values)
if 'price_unit' in values:
for line in lines:
@@ -329,7 +330,8 @@ class PurchaseOrderLine(models.Model):
moves = line.move_ids.filtered(lambda s: s.state not in ('cancel', 'done') and s.product_id == line.product_id)
moves.write({'price_unit': line._get_stock_move_price_unit()})
if 'product_qty' in values:
lines.with_context(previous_product_qty=previous_product_qty)._create_or_update_picking()
lines = lines.filtered(lambda l: float_compare(previous_product_qty[l.id], l.product_qty, precision_rounding=l.product_uom.rounding) != 0)
lines.with_context(previous_product_qty=previous_product_uom_qty)._create_or_update_picking()
return result
def unlink(self):

View File

@@ -44,12 +44,13 @@ class SaleOrder(models.Model):
return order
def action_confirm(self):
res = super().action_confirm()
valid_coupon_ids = self.generated_coupon_ids.filtered(lambda coupon: coupon.state not in ['expired', 'cancel'])
valid_coupon_ids.write({'state': 'new', 'partner_id': self.partner_id})
(self.generated_coupon_ids - valid_coupon_ids).write({'state': 'cancel', 'partner_id': self.partner_id})
self.applied_coupon_ids.write({'state': 'used'})
self._send_reward_coupon_mail()
return super(SaleOrder, self).action_confirm()
return res
def _action_cancel(self):
res = super()._action_cancel()

View File

@@ -2,11 +2,9 @@
<flectra>
<template id="report_delivery_document_inherit_sale_stock" inherit_id="stock.report_delivery_document">
<xpath expr="//div[@name='div_sched_date']" position="after">
<div class="row justify-content-end" t-if="o.sudo().sale_id.client_order_ref">
<div class="col-auto">
<strong>Customer Reference:</strong>
<p t-field="o.sudo().sale_id.client_order_ref"/>
</div>
<div class="col-auto justify-content-end" t-if="o.sudo().sale_id.client_order_ref">
<strong>Customer Reference:</strong>
<p t-field="o.sudo().sale_id.client_order_ref"/>
</div>
</xpath>
</template>

View File

@@ -36,9 +36,9 @@ var StockOrderpointListController = ListController.extend({
_onButtonClicked: function (ev) {
if (ev.data.attrs.class.split(' ').includes('o_replenish_buttons')) {
ev.stopPropagation();
var self = this;
this._callButtonAction(ev.data.attrs, ev.data.record).then(function () {
self.reload();
this.trigger_up('save_line', {
recordID: ev.data.record.id,
onSuccess: () => this._callButtonAction(ev.data.attrs, ev.data.record).then(() => this.reload())
});
} else {
this._super.apply(this, arguments);

View File

@@ -18,7 +18,7 @@
<field name="location_id" options="{'no_create': True}" attrs="{'invisible': [('picking_type_code', '=', 'incoming')]}" groups="stock.group_stock_multi_locations"/>
<field name="location_dest_id" options="{'no_create': True}" attrs="{'invisible': [('picking_type_code', '=', 'outgoing')]}" groups="stock.group_stock_multi_locations"/>
<field name="is_done"/>
<field name="company_id" groups="base.main_company"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
<field name="move_ids" attrs="{'invisible': [('state', 'in', ('new', 'draft', 'assigned', 'done'))]}">
<tree>

View File

@@ -296,6 +296,7 @@
<template id="question_container" name="Survey: question container">
<t t-set="display_question"
t-value="survey.questions_layout == 'page_per_question'
or survey.questions_selection == 'random'
or (survey.questions_layout == 'one_page' and not question.is_conditional)
or (survey.questions_layout == 'page_per_section' and (not question.is_conditional or triggering_answer_by_question[question.id] in selected_answers))"/>

View File

@@ -98,7 +98,7 @@ class Web_Editor(http.Controller):
@http.route('/web_editor/checklist', type='json', auth='user')
def update_checklist(self, res_model, res_id, filename, checklistId, checked, **kwargs):
record = request.env[res_model].browse(res_id)
value = getattr(record, filename, False)
value = filename in record._fields and record[filename]
htmlelem = etree.fromstring("<div>%s</div>" % value, etree.HTMLParser())
checked = bool(checked)

View File

@@ -571,7 +571,7 @@
<span class="badge badge-danger font-weight-normal px-2" t-if="not user['website_published']">Unpublished</span>
<strong class="text-muted" t-esc="user['rank']"/>
<div class="h3 my-2" t-if="user['karma_gain']">
<span class="badge badge-pill badge-success px-3 py-2" >+ <t t-esc="user['karma_gain']"/> XP</span>
<span class="badge badge-pill badge-success px-3 py-2" ><t t-if="user['karma_gain'] > 0">+ </t> <t t-esc="user['karma_gain']"/> XP</span>
</div>
</div>
<div class="row mx-0 o_wprofile_top3_card_footer text-nowrap">
@@ -594,7 +594,7 @@
</td>
<td class="align-middle text-nowrap">
<t t-if="user['karma_gain']">
<span class="badge badge-pill badge-success d-inline">+ <t t-esc="user['karma_gain']"/> XP</span>
<span class="badge badge-pill badge-success d-inline"><t t-if="user['karma_gain'] > 0">+ </t><t t-esc="user['karma_gain']"/> XP</span>
<span class="text-muted pl-2 pr-3">
<t t-if="group_by == 'week'">this week</t>
<t t-elif="group_by == 'month'">this month</t>