mirror of
https://gitlab.com/flectra-hq/flectra.git
synced 2025-02-25 18:55:21 -06:00
[PATCH] Upstream patch - 03042023
This commit is contained in:
@@ -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):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
@@ -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
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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))"/>
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user