diff --git a/addons/base_vat/models/res_partner.py b/addons/base_vat/models/res_partner.py
index f2e097b6a4..debca5773c 100644
--- a/addons/base_vat/models/res_partner.py
+++ b/addons/base_vat/models/res_partner.py
@@ -148,21 +148,35 @@ class ResPartner(models.Model):
for partner in self:
if not partner.vat:
continue
- #check with country code as prefix of the TIN
- vat_country, vat_number = self._split_vat(partner.vat)
+
if company.vat_check_vies and partner.commercial_partner_id.country_id in eu_countries:
# force full VIES online check
check_func = self.vies_vat_check
else:
# quick and partial off-line checksum validation
check_func = self.simple_vat_check
- if not check_func(vat_country, vat_number):
- #if fails, check with country code from country
- country_code = partner.commercial_partner_id.country_id.code
- if country_code:
- if not check_func(country_code.lower(), partner.vat):
- msg = partner._construct_constraint_msg(country_code.lower())
- raise ValidationError(msg)
+
+ failed_check = False
+ #check with country code as prefix of the TIN
+ vat_country_code, vat_number = self._split_vat(partner.vat)
+ vat_guessed_country = self.env['res.country'].search([('code', '=', vat_country_code.upper())])
+ if vat_guessed_country:
+ failed_check = not check_func(vat_country_code, vat_number)
+
+ #if fails, check with country code from country
+ partner_country_code = partner.commercial_partner_id.country_id.code
+ if (not vat_guessed_country or failed_check) and partner_country_code:
+ failed_check = not check_func(partner_country_code.lower(), partner.vat)
+
+ # We allow any number if it doesn't start with a country code and the partner has no country.
+ # This is necessary to support an ORM limitation: setting vat and country_id together on a company
+ # triggers two distinct write on res.partner, one for each field, both triggering this constraint.
+ # If vat is set before country_id, the constraint must not break.
+
+ if failed_check:
+ country_code = partner_country_code or vat_country_code
+ msg = partner._construct_constraint_msg(country_code.lower() if country_code else None)
+ raise ValidationError(msg)
def _construct_constraint_msg(self, country_code):
self.ensure_one()
diff --git a/addons/base_vat/tests/test_validate_ruc.py b/addons/base_vat/tests/test_validate_ruc.py
index 94e26b36b2..2b4f9b11ad 100644
--- a/addons/base_vat/tests/test_validate_ruc.py
+++ b/addons/base_vat/tests/test_validate_ruc.py
@@ -55,9 +55,32 @@ class TestStructure(SavepointCase):
# reactivate it and correct the vat number
with patch('flectra.addons.base_vat.models.res_partner.check_vies', type(self)._vies_check_func):
self.env.user.company_id.vat_check_vies = True
- with self.assertRaises(ValidationError), self.env.cr.savepoint():
- company.vat = "BE0987654321" # VIES refused, don't fallback on other check
- company.vat = "BE0477472701"
+
+ def test_vat_syntactic_validation(self):
+ """ Tests VAT validation (both successes and failures), with the different country
+ detection cases possible.
+ """
+ test_partner =self.env['res.partner'].create({'name': "John Dex"})
+
+ # VAT starting with country code: use the starting country code
+ test_partner.write({'vat': 'BE0477472701', 'country_id': self.env.ref('base.fr').id})
+ test_partner.write({'vat': 'BE0477472701', 'country_id': None})
+
+ with self.assertRaises(ValidationError):
+ test_partner.write({'vat': 'BE42', 'country_id': self.env.ref('base.fr').id})
+
+ with self.assertRaises(ValidationError):
+ test_partner.write({'vat': 'BE42', 'country_id': None})
+
+ # No country code in VAT: use the partner's country
+ test_partner.write({'vat': '0477472701', 'country_id': self.env.ref('base.be').id})
+
+ with self.assertRaises(ValidationError):
+ test_partner.write({'vat': '42', 'country_id': self.env.ref('base.be').id})
+
+ # If no country can be guessed: VAT number should always be considered valid
+ # (for technical reasons due to ORM and res.company making related fields towards res.partner for country_id and vat)
+ test_partner.write({'vat': '0477472701', 'country_id': None})
@tagged('-standard', 'external')
diff --git a/addons/calendar/models/calendar_event.py b/addons/calendar/models/calendar_event.py
index a4523198f9..df30f23cbd 100644
--- a/addons/calendar/models/calendar_event.py
+++ b/addons/calendar/models/calendar_event.py
@@ -717,11 +717,12 @@ class Meeting(models.Model):
events = super().create(other_vals)
for vals in recurring_vals:
-
- recurrence_values = {field: vals.pop(field) for field in recurrence_fields if field in vals}
vals['follow_recurrence'] = True
- event = super().create(vals)
- events |= event
+ recurring_events = super().create(recurring_vals)
+ events += recurring_events
+
+ for event, vals in zip(recurring_events, recurring_vals):
+ recurrence_values = {field: vals.pop(field) for field in recurrence_fields if field in vals}
if vals.get('recurrency'):
detached_events = event._apply_recurrence_values(recurrence_values)
detached_events.active = False
diff --git a/addons/calendar/models/calendar_recurrence.py b/addons/calendar/models/calendar_recurrence.py
index c6dfdef34f..f0030395e4 100644
--- a/addons/calendar/models/calendar_recurrence.py
+++ b/addons/calendar/models/calendar_recurrence.py
@@ -118,7 +118,13 @@ class RecurrenceRule(models.Model):
until = fields.Date('Repeat Until')
_sql_constraints = [
- ('month_day', "CHECK (rrule_type != 'monthly' OR month_by != 'day' OR day >= 1 AND day <= 31)", "The day must be between 1 and 31"),
+ ('month_day',
+ "CHECK (rrule_type != 'monthly' "
+ "OR month_by != 'day' "
+ "OR day >= 1 AND day <= 31 "
+ "OR weekday in %s AND byday in %s)"
+ % (tuple(wd[0] for wd in WEEKDAY_SELECTION), tuple(bd[0] for bd in BYDAY_SELECTION)),
+ "The day must be between 1 and 31"),
]
@api.depends('rrule')
diff --git a/addons/calendar_sms/security/sms_security.xml b/addons/calendar_sms/security/sms_security.xml
index d174f4f0d0..87343a6571 100644
--- a/addons/calendar_sms/security/sms_security.xml
+++ b/addons/calendar_sms/security/sms_security.xml
@@ -1,9 +1,11 @@
- SMS Template: system administrator CRUD on calendar event templates
+ SMS Template: unnecessary rule for system group
- [('model_id.model', '=', 'calendar.event')]
+
+ [(1, '=', 1)]
+
diff --git a/addons/coupon/data/coupon_email_data.xml b/addons/coupon/data/coupon_email_data.xml
index 7283575cf3..1bb2f1913c 100644
--- a/addons/coupon/data/coupon_email_data.xml
+++ b/addons/coupon/data/coupon_email_data.xml
@@ -7,6 +7,7 @@
Your reward coupon from ${object.program_id.company_id.name}
${object.program_id.company_id.email | safe}
${object.order_id.partner_id.id or object.partner_id.id}
+
${object.partner_id.lang}
|
diff --git a/addons/coupon/wizard/coupon_generate.py b/addons/coupon/wizard/coupon_generate.py
index 68c48c0ade..8141227afa 100644
--- a/addons/coupon/wizard/coupon_generate.py
+++ b/addons/coupon/wizard/coupon_generate.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-from flectra import api, fields, models
+from flectra import _, api, fields, models
import ast
from flectra.osv import expression
@@ -33,7 +33,9 @@ class CouponGenerate(models.TransientModel):
for partner in self.env['res.partner'].search(ast.literal_eval(self.partners_domain)):
vals.update({'partner_id': partner.id, 'state': 'sent' if partner.email else 'new'})
coupon = self.env['coupon.coupon'].create(vals)
- subject = '%s, a coupon has been generated for you' % (partner.name)
+ context = dict(lang=partner.lang)
+ subject = _('%s, a coupon has been generated for you') % (partner.name)
+ del context
template = self.env.ref('coupon.mail_template_sale_coupon', raise_if_not_found=False)
if template:
email_values = {'email_to': partner.email, 'email_from': self.env.user.email or '', 'subject': subject}
diff --git a/addons/crm/README.md b/addons/crm/README.md
index 8ba45388db..898a08fbe2 100644
--- a/addons/crm/README.md
+++ b/addons/crm/README.md
@@ -1,8 +1,8 @@
-Odoo CRM
+Flectra CRM
--------
-Boost sales productivity, improve win rates, grow revenue with the Odoo
-Open Source CRM.
+Boost sales productivity, improve win rates, grow revenue with the Flectra
+Open Source CRM.
Manage your sales funnel with no effort. Attract leads, follow-up on phone
calls and meetings. Analyse the quality of your leads to make informed
@@ -41,7 +41,7 @@ Work with the email applications you already use every day. Whether your
company uses Microsoft Outlook or Gmail, no one needs to change the way they
work, so everyone stays productive.
-Route, sort and filter incoming emails automatically. Odoo CRM handles incoming
+Route, sort and filter incoming emails automatically. Flectra CRM handles incoming
emails and route them to the right opportunities or Sales Team. New leads are
created on the fly and interested salespersons are notified automatically.
@@ -55,7 +55,7 @@ see what your team is busy with.
Lead Automation and Marketing Campaigns
---------------------------------------
-Drive performance by automating tasks with Odoo CRM.
+Drive performance by automating tasks with Flectra CRM.
Use our marketing campaigns to automate lead acquisition, follow ups and
promotions. Define automated actions (e.g. ask a salesperson to call, send an
diff --git a/addons/crm/__manifest__.py b/addons/crm/__manifest__.py
index adcb4fb2f8..63825a11d9 100644
--- a/addons/crm/__manifest__.py
+++ b/addons/crm/__manifest__.py
@@ -8,7 +8,7 @@
'sequence': 15,
'summary': 'Track leads and close opportunities',
'description': "",
- 'website': 'https://flectrahq.com/crm',
+ 'website': 'https://www.flectrahq.com/page/crm',
'depends': [
'base_setup',
'sales_team',
diff --git a/addons/crm/data/mail_activity_demo.xml b/addons/crm/data/mail_activity_demo.xml
index 32536f8650..2a5af498b7 100644
--- a/addons/crm/data/mail_activity_demo.xml
+++ b/addons/crm/data/mail_activity_demo.xml
@@ -92,7 +92,7 @@
|
|
- Powered by Flectra
+ Powered by Flectra
|
diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py
index 6a22ce60dc..b97c8f8f42 100644
--- a/addons/crm/models/crm_lead.py
+++ b/addons/crm/models/crm_lead.py
@@ -1045,13 +1045,13 @@ class Lead(models.Model):
self.ensure_one()
for opportunity in opportunities:
for message in opportunity.message_ids:
+ if message.subject:
+ subject = _("From %(source_name)s : %(source_subject)s", source_name=opportunity.name, source_subject=message.subject)
+ else:
+ subject = _("From %(source_name)s", source_name=opportunity.name)
message.write({
'res_id': self.id,
- 'subject': _(
- "From %(source_name)s : %(source_subject)s",
- source_name=opportunity.name,
- source_subject=message.subject
- )
+ 'subject': subject,
})
return True
diff --git a/addons/crm/static/src/js/tours/crm.js b/addons/crm/static/src/js/tours/crm.js
index e02bab7a82..fdd743025c 100644
--- a/addons/crm/static/src/js/tours/crm.js
+++ b/addons/crm/static/src/js/tours/crm.js
@@ -60,7 +60,7 @@ tour.register('crm_tour', {
}, {
trigger: '.modal-footer button[name="action_close_dialog"]',
content: _t("All set. Let’s
Schedule it."),
- position: "bottom",
+ position: "top", // dot NOT move to bottom, it would cause a resize flicker, see task-2476595
run: function (actions) {
actions.auto('.modal-footer button[special=cancel]');
},
diff --git a/addons/crm/tests/test_crm_lead.py b/addons/crm/tests/test_crm_lead.py
index 0ccca89c1a..9babe1caf1 100644
--- a/addons/crm/tests/test_crm_lead.py
+++ b/addons/crm/tests/test_crm_lead.py
@@ -121,7 +121,7 @@ class TestCRMLead(TestCrmCommon):
'street': 'My street',
'street2': 'My street',
'city': 'My city',
- 'zip': 'test@flectra.com',
+ 'zip': 'test@flectrahq.com',
'state_id': self.env['res.country.state'].create({
'name': 'My state',
'country_id': self.country_ref.id,
diff --git a/addons/crm/views/crm_lead_views.xml b/addons/crm/views/crm_lead_views.xml
index a8ebee8aff..c415ff4220 100644
--- a/addons/crm/views/crm_lead_views.xml
+++ b/addons/crm/views/crm_lead_views.xml
@@ -118,7 +118,7 @@
@@ -267,7 +267,7 @@