diff --git a/addons/account/data/email_template_data_invoice.xml b/addons/account/data/email_template_data_invoice.xml index 96e55557c1e..41731b7c527 100644 --- a/addons/account/data/email_template_data_invoice.xml +++ b/addons/account/data/email_template_data_invoice.xml @@ -16,7 +16,7 @@ Invoice_${(object.number or '').replace('/','_')}_${object.state == 'draft' and 'draft' or ''} ${object.partner_id.lang} Dear ${object.partner_id.name} +

Dear ${object.partner_id.name} % set access_action = object.get_access_action() % set access_url = access_action['type'] == 'ir.actions.act_url' and access_action['url'] or '/report/pdf/account.report_invoice/' + str(object.id) % set is_online = access_action and access_action['type'] == 'ir.actions.act_url' @@ -69,7 +69,7 @@ from ${object.company_id.name}. - +
@@ -100,7 +100,7 @@ from ${object.company_id.name}.
- +
@@ -117,14 +117,14 @@ ${object.event_id.user_id.partner_id.name} invited you for the ${object.event_id.name} meeting of ${object.event_id.user_id.company_id.name}.

${user.company_id.name}
diff --git a/addons/account/models/account.py b/addons/account/models/account.py index 03fc3f6203e..0271cad8aa5 100644 --- a/addons/account/models/account.py +++ b/addons/account/models/account.py @@ -136,6 +136,13 @@ class AccountAccount(models.Model): for account in self: if (account.company_id.id <> vals['company_id']) and move_lines: raise UserError(_('You cannot change the owner company of an account that already contains journal items.')) + # If user change the reconcile flag, all aml should be recomputed for that account and this is very costly. + # So to prevent some bugs we add a constraint saying that you cannot change the reconcile field if there is any aml existing + # for that account. + if vals.get('reconcile'): + move_lines = self.env['account.move.line'].search([('account_id', 'in', self.ids)], limit=1) + if len(move_lines): + raise UserError(_('You cannot change the value of the reconciliation on this account as it already has some moves')) return super(AccountAccount, self).write(vals) @api.multi diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index def1b9484f3..95ae08e8103 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -1425,7 +1425,7 @@ class AccountPaymentTerm(models.Model): class AccountPaymentTermLine(models.Model): _name = "account.payment.term.line" _description = "Payment Term Line" - _order = "sequence" + _order = "sequence, id" value = fields.Selection([ ('balance', 'Balance'), @@ -1433,7 +1433,7 @@ class AccountPaymentTermLine(models.Model): ('fixed', 'Fixed Amount') ], string='Type', required=True, default='balance', help="Select here the kind of valuation related to this payment term line.") - value_amount = fields.Float(string='Value', digits=dp.get_precision('Payment Term'), help="For percent enter a ratio between 0-100.") + value_amount = fields.Float(string='Value', digits=dp.get_precision('Payment Terms'), help="For percent enter a ratio between 0-100.") days = fields.Integer(string='Number of Days', required=True, default=0) option = fields.Selection([ ('day_after_invoice_date', 'Day(s) after the invoice date'), diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index 3b6ab044353..b9d80ea21cc 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -885,6 +885,8 @@ class AccountMoveLine(models.Model): vals['date'] = self._context.get('date_p') or time.strftime('%Y-%m-%d') if 'name' not in vals: vals['name'] = self._context.get('comment') or _('Write-Off') + if 'analytic_account_id' not in vals: + vals['analytic_account_id'] = self.env.context.get('analytic_id', False) #compute the writeoff amount if not given if 'credit' not in vals and 'debit' not in vals: amount = sum([r.amount_residual for r in self]) @@ -901,8 +903,8 @@ class AccountMoveLine(models.Model): # Writeoff line in the account of self first_line_dict = vals.copy() first_line_dict['account_id'] = self[0].account_id.id - if 'analytic_account_id' in vals: - del vals['analytic_account_id'] + if 'analytic_account_id' in first_line_dict: + del first_line_dict['analytic_account_id'] # Writeoff line in specified writeoff account second_line_dict = vals.copy() diff --git a/addons/account/report/account_aged_partner_balance.py b/addons/account/report/account_aged_partner_balance.py index fb5f90f96ec..737a07e298a 100644 --- a/addons/account/report/account_aged_partner_balance.py +++ b/addons/account/report/account_aged_partner_balance.py @@ -92,11 +92,12 @@ class ReportAgedPartnerBalance(models.AbstractModel): for partial_line in line.matched_credit_ids: if partial_line.create_date[:10] <= date_from: line_amount -= partial_line.amount - undue_amounts[partner_id] += line_amount - lines[partner_id].append({ - 'line': line, - 'amount': line_amount, - 'period': 6, + if not self.env.user.company_id.currency_id.is_zero(line_amount): + undue_amounts[partner_id] += line_amount + lines[partner_id].append({ + 'line': line, + 'amount': line_amount, + 'period': 6, }) # Use one query per period and store results in history (a list variable) @@ -144,12 +145,13 @@ class ReportAgedPartnerBalance(models.AbstractModel): if partial_line.create_date[:10] <= date_from: line_amount -= partial_line.amount - partners_amount[partner_id] += line_amount - lines[partner_id].append({ - 'line': line, - 'amount': line_amount, - 'period': i + 1, - }) + if not self.env.user.company_id.currency_id.is_zero(line_amount): + partners_amount[partner_id] += line_amount + lines[partner_id].append({ + 'line': line, + 'amount': line_amount, + 'period': i + 1, + }) history.append(partners_amount) for partner in partners: diff --git a/addons/account/static/description/index.html b/addons/account/static/description/index.html index fc041102c0b..844515c9b6b 100644 --- a/addons/account/static/description/index.html +++ b/addons/account/static/description/index.html @@ -1,6 +1,6 @@
-

Turn Quotes Into Invoices with minimal effort

+

Turn Quotes Into Invoices with minimal effort

Bill automatically based on SO, delivery orders, contracts or time and material

@@ -28,7 +28,7 @@
-

Get paid faster

+

Get paid faster

Online payments and automated follow-ups

Accept online payments via Paypal, Ogone, Buckaroo or Adyen. Eliminate the stress of sending reminders for late or outstanding payments. Simply set up and automate follow-ups to get paid quickly. @@ -41,7 +41,7 @@

-

Simplify your accounting

+

Simplify your accounting

Keep track of bank account movements and the status of invoices

Use a status overview to help keep track of drafts, paid and unpaid invoices. Automatically record all transactions from your bank accounts by importing and reconciling your bank statements. @@ -54,7 +54,7 @@

-

Analyze your sales

+

Analyze your sales

Access insightful data to get the big picture

diff --git a/addons/account/views/account_menuitem.xml b/addons/account/views/account_menuitem.xml index 99b203bac02..0ebd9110934 100644 --- a/addons/account/views/account_menuitem.xml +++ b/addons/account/views/account_menuitem.xml @@ -24,8 +24,7 @@ - - + diff --git a/addons/account/views/account_payment_view.xml b/addons/account/views/account_payment_view.xml index dfa029a8c67..1f12802b74c 100644 --- a/addons/account/views/account_payment_view.xml +++ b/addons/account/views/account_payment_view.xml @@ -98,10 +98,7 @@ -

- - -
+ diff --git a/addons/account/views/partner_view.xml b/addons/account/views/partner_view.xml index beda8f4797c..f4bb90a1236 100644 --- a/addons/account/views/partner_view.xml +++ b/addons/account/views/partner_view.xml @@ -39,12 +39,12 @@ - +
- +
diff --git a/addons/account/views/web_planner_data.xml b/addons/account/views/web_planner_data.xml index c2dc450f6a1..ff796c2859b 100644 --- a/addons/account/views/web_planner_data.xml +++ b/addons/account/views/web_planner_data.xml @@ -44,14 +44,24 @@ Your Company

- The first step is to set up your company information. This is mostly used for invoicing. + The first step is to set up your company's information. This is mostly used in printed business documents like orders and invoices.

+ + + + @@ -62,17 +72,17 @@ t-options='{"widget": "contact", "fields": ["address"], "no_marker": True}'/> - + @@ -80,7 +90,7 @@ @@ -88,7 +98,7 @@ @@ -101,15 +111,6 @@ - - -
Logo + set company logo + + + Change + +
Legal Name
- + Change - + Change
Tax ID (VAT)Tax ID - + Change Main Currency - + Change Terms & Conditions - + Change Review Terms
Logo - set company logo - - - Change - -

diff --git a/addons/account/wizard/account_report_partner_ledger_view.xml b/addons/account/wizard/account_report_partner_ledger_view.xml index d50e048de63..57d09ccf049 100644 --- a/addons/account/wizard/account_report_partner_ledger_view.xml +++ b/addons/account/wizard/account_report_partner_ledger_view.xml @@ -40,7 +40,7 @@ diff --git a/addons/account/wizard/account_report_print_journal_view.xml b/addons/account/wizard/account_report_print_journal_view.xml index e40cac57f26..f549e683934 100644 --- a/addons/account/wizard/account_report_print_journal_view.xml +++ b/addons/account/wizard/account_report_print_journal_view.xml @@ -32,7 +32,7 @@ diff --git a/addons/account_accountant/static/description/index.html b/addons/account_accountant/static/description/index.html index 52d49f99ac0..d6d09a0c028 100644 --- a/addons/account_accountant/static/description/index.html +++ b/addons/account_accountant/static/description/index.html @@ -1,6 +1,6 @@
-

Beautiful accounting software

+

Beautiful accounting software

Do more, in less time.

@@ -12,7 +12,7 @@
-

Popular features that will change your life

+

Popular features that will change your life

All the time-saving tools you need to grow your business.

@@ -30,7 +30,7 @@ Create sharp and professional invoices, manage recurring billings, and easily track payments.

-
+
@@ -52,7 +52,7 @@
-

Get paid faster

+

Get paid faster

Electronic invoicing and automated follow-ups

@@ -75,7 +75,7 @@

-

Beautiful dynamic statements

+

Beautiful dynamic statements

Your reports, the way you like them.

diff --git a/addons/account_asset/views/account_asset_views.xml b/addons/account_asset/views/account_asset_views.xml index 736840df6d8..0b5e7b0de1f 100644 --- a/addons/account_asset/views/account_asset_views.xml +++ b/addons/account_asset/views/account_asset_views.xml @@ -103,7 +103,7 @@
${user.signature | safe}

- Sent by ${user.company_id.name} using Odoo + Sent by ${user.company_id.name} using Odoo

]]> diff --git a/addons/base_action_rule/models/base_action_rule.py b/addons/base_action_rule/models/base_action_rule.py index 7eacdb4ed8b..9c0151fba53 100644 --- a/addons/base_action_rule/models/base_action_rule.py +++ b/addons/base_action_rule/models/base_action_rule.py @@ -124,7 +124,7 @@ class BaseActionRule(models.Model): def _update_registry(self): """ Update the registry after a modification on action rules. """ - if self.env.registry.ready: + if self.env.registry.ready and not self.env.context.get('import_file'): # for the sake of simplicity, simply force the registry to reload self._cr.commit() self.env.reset() diff --git a/addons/base_import/models/base_import.py b/addons/base_import/models/base_import.py index 3f872edfc26..32dfcbd0cf9 100644 --- a/addons/base_import/models/base_import.py +++ b/addons/base_import/models/base_import.py @@ -649,7 +649,7 @@ class Import(models.TransientModel): }] _logger.info('importing %d rows...', len(data)) - import_result = self.env[self.res_model].load(import_fields, data) + import_result = self.env[self.res_model].with_context(import_file=True).load(import_fields, data) _logger.info('done') # If transaction aborted, RELEASE SAVEPOINT is going to raise diff --git a/addons/base_import_module/models/ir_module.py b/addons/base_import_module/models/ir_module.py index 230ffeb0f7f..6e7c404df39 100644 --- a/addons/base_import_module/models/ir_module.py +++ b/addons/base_import_module/models/ir_module.py @@ -45,6 +45,10 @@ class IrModule(models.Model): for kind in ['data', 'init_xml', 'update_xml']: for filename in terp[kind]: + ext = os.path.splitext(filename)[1].lower() + if ext not in ('.xml', '.csv', '.sql'): + _logger.info("module %s: skip unsupported file %s", module, filename) + continue _logger.info("module %s: loading %s", module, filename) noupdate = False if filename.endswith('.csv') and kind in ('init', 'init_xml'): diff --git a/addons/calendar/data/calendar_data.xml b/addons/calendar/data/calendar_data.xml index 80e96125b2d..d4e83364aba 100644 --- a/addons/calendar/data/calendar_data.xml +++ b/addons/calendar/data/calendar_data.xml @@ -90,9 +90,9 @@

- Accept - Decline - View + Accept + Decline + View

@@ -209,14 +209,14 @@

-
+
${object.event_id.get_interval('dayname', tz=object.partner_id.tz if not object.event_id.allday else None)}
-
+
${object.event_id.get_interval('day', tz=object.partner_id.tz if not object.event_id.allday else None)}
-
${object.event_id.get_interval('month', tz=object.partner_id.tz if not object.event_id.allday else None)}
-
${not object.event_id.allday and object.event_id.get_interval('time', tz=object.partner_id.tz) or ''}
+
${object.event_id.get_interval('month', tz=object.partner_id.tz if not object.event_id.allday else None)}
+
${not object.event_id.allday and object.event_id.get_interval('time', tz=object.partner_id.tz) or ''}
@@ -181,9 +181,9 @@

- Accept - Decline - View + Accept + Decline + View

@@ -301,14 +301,14 @@

-
+
${object.event_id.get_interval('dayname', tz=object.partner_id.tz if not object.event_id.allday else None)}
-
+
${object.event_id.get_interval('day', tz=object.partner_id.tz if not object.event_id.allday else None)}
-
${object.event_id.get_interval('month', tz=object.partner_id.tz if not object.event_id.allday else None)}
-
${not object.event_id.allday and object.event_id.get_interval('time', tz=object.partner_id.tz) or ''}
+
${object.event_id.get_interval('month', tz=object.partner_id.tz if not object.event_id.allday else None)}
+
${not object.event_id.allday and object.event_id.get_interval('time', tz=object.partner_id.tz) or ''}
@@ -273,9 +273,9 @@

- Accept - Decline - View + Accept + Decline + View

@@ -31,7 +31,7 @@
-
+
${object.event_id.get_interval('dayname', tz=object.partner_id.tz if not object.event_id.allday else None)}
-
+
${object.event_id.get_interval('day', tz=object.partner_id.tz if not object.event_id.allday else None)}
-
${object.event_id.get_interval('month', tz=object.partner_id.tz if not object.event_id.allday else None)}
-
${not object.event_id.allday and object.event_id.get_interval('time', tz=object.partner_id.tz) or ''}
+
${object.event_id.get_interval('month', tz=object.partner_id.tz if not object.event_id.allday else None)}
+
${not object.event_id.allday and object.event_id.get_interval('time', tz=object.partner_id.tz) or ''}
diff --git a/addons/calendar/models/calendar.py b/addons/calendar/models/calendar.py index d3e1a71555b..01b5a2c714d 100644 --- a/addons/calendar/models/calendar.py +++ b/addons/calendar/models/calendar.py @@ -376,7 +376,7 @@ class AlarmManager(models.AbstractModel): if meeting.recurrency: b_found = False last_found = False - for one_date in meeting.get_recurrent_date_by_event(): + for one_date in meeting._get_recurrent_date_by_event(): in_date_format = one_date.replace(tzinfo=None) last_found = self.do_check_alarm_for_one_date(in_date_format, meeting, max_delta, time_limit, 'notification', after=partner.calendar_last_notif_ack) if last_found: diff --git a/addons/crm/__init__.py b/addons/crm/__init__.py index d84fb182e8f..859f7ca8e08 100644 --- a/addons/crm/__init__.py +++ b/addons/crm/__init__.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +import controllers import models import report import wizard diff --git a/addons/crm/controllers/__init__.py b/addons/crm/controllers/__init__.py new file mode 100644 index 00000000000..12d2d759f34 --- /dev/null +++ b/addons/crm/controllers/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -* +import main diff --git a/addons/crm/controllers/main.py b/addons/crm/controllers/main.py new file mode 100644 index 00000000000..d92758e961b --- /dev/null +++ b/addons/crm/controllers/main.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo.addons.mail.controllers.main import MailController +from odoo import http + + +class CrmController(http.Controller): + + @http.route('/lead/case_mark_won', type='http', auth='user', methods=['GET']) + def crm_lead_case_mark_won(self, res_id, token): + comparison, record, redirect = MailController._check_token_and_record_or_redirect('crm.lead', int(res_id), token) + if comparison and record: + try: + record.case_mark_won() + except Exception: + return MailController._redirect_to_messaging() + return redirect + + @http.route('/lead/case_mark_lost', type='http', auth='user', methods=['GET']) + def crm_lead_case_mark_lost(self, res_id, token): + comparison, record, redirect = MailController._check_token_and_record_or_redirect('crm.lead', int(res_id), token) + if comparison and record: + try: + record.case_mark_lost() + except Exception: + return MailController._redirect_to_messaging() + return redirect + + @http.route('/lead/convert', type='http', auth='user', methods=['GET']) + def crm_lead_convert(self, res_id, token): + comparison, record, redirect = MailController._check_token_and_record_or_redirect('crm.lead', int(res_id), token) + if comparison and record: + try: + record.convert_opportunity(record.partner_id.id) + except Exception: + return MailController._redirect_to_messaging() + return redirect diff --git a/addons/crm/data/mail_template_data.xml b/addons/crm/data/mail_template_data.xml index 2797493b1f3..ac84bea844c 100644 --- a/addons/crm/data/mail_template_data.xml +++ b/addons/crm/data/mail_template_data.xml @@ -22,7 +22,7 @@ - Auto Login + Auto Login
-

Hooray!


+

Hooray!


Your Odoo CRM application is up and running.


@@ -56,7 +56,7 @@ CRM planner to activate extra features - (${user.env['web.planner'].get_planner_progress('planner_crm')}% done) + (${user.env['web.planner'].get_planner_progress('planner_crm')}% done)

  • diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py index d57a03ab880..e24e3404042 100644 --- a/addons/crm/models/crm_lead.py +++ b/addons/crm/models/crm_lead.py @@ -1038,11 +1038,11 @@ class Lead(FormatAddress, models.Model): self.ensure_one() if self.type == 'lead': - convert_action = self._notification_link_helper('method', method='convert_opportunity', partner_id=self.partner_id.id) + convert_action = self._notification_link_helper('controller', controller='/lead/convert') salesman_actions = [{'url': convert_action, 'title': _('Convert to opportunity')}] else: - won_action = self._notification_link_helper('method', method='action_set_won') - lost_action = self._notification_link_helper('method', method='action_set_lost') + won_action = self._notification_link_helper('controller', controller='/lead/case_mark_won') + lost_action = self._notification_link_helper('controller', controller='/lead/case_mark_lost') salesman_actions = [ {'url': won_action, 'title': _('Won')}, {'url': lost_action, 'title': _('Lost')}] diff --git a/addons/crm/static/description/index.html b/addons/crm/static/description/index.html index 1805a0c84fc..df8b9a447f8 100644 --- a/addons/crm/static/description/index.html +++ b/addons/crm/static/description/index.html @@ -1,6 +1,6 @@
    -

    The real customer centric CRM

    +

    The real customer centric CRM

    Track leads, close opportunities and get accurate forecasts.

    @@ -10,7 +10,7 @@
    -

    Get accurate forecasts

    +

    Get accurate forecasts

    Use actionable data to make better decisions

    @@ -34,7 +34,7 @@
    -

    Get more done in less time

    +

    Get more done in less time

    Great Tool = Happy People

    @@ -51,7 +51,7 @@
    -

    Real-time overview

    +

    Real-time overview

    of your performances & next activities

    Know exactly how you perform compared to your monthly targets.
    Organize your work based on your scheduled next actions. @@ -65,7 +65,7 @@

    -

    Stay connected to your customers

    +

    Stay connected to your customers

    Real-time messaging to enhance collaboration

    diff --git a/addons/crm/views/crm_lead_views.xml b/addons/crm/views/crm_lead_views.xml index f3071eb0a6c..2abb85b6ea8 100644 --- a/addons/crm/views/crm_lead_views.xml +++ b/addons/crm/views/crm_lead_views.xml @@ -526,7 +526,7 @@ - + diff --git a/addons/event/data/email_template_data.xml b/addons/event/data/email_template_data.xml index cd675b35a69..97f5b081fea 100644 --- a/addons/event/data/email_template_data.xml +++ b/addons/event/data/email_template_data.xml @@ -55,7 +55,7 @@

    Hi ${object.name}, here is your order confirmation for % if 'website_url' in object.event_id and object.event_id.website_url: - ${object.event_id.name} + ${object.event_id.name} % else: ${object.event_id.name} % endif @@ -110,7 +110,7 @@

Questions about this event?

-
Please contact the organizer at ${object.event_id.organizer_id.email}
+
Please contact the organizer at ${object.event_id.organizer_id.email}
@@ -119,9 +119,9 @@
Add this event to your calendar - Google - Outlook - Yahoo + Google + Outlook + Yahoo
@@ -138,7 +138,7 @@ -

This email was sent to ${object.email}
+
This email was sent to ${object.email}

Copyright © ${object.company_id.name}

@@ -186,7 +186,7 @@

Please keep in mind that the event % if 'website_url' in object.event_id and object.event_id.website_url: - ${object.event_id.name} + ${object.event_id.name} % else: ${object.event_id.name} % endif @@ -243,7 +243,7 @@

Questions about this event?

-
Please contact the organizer at ${object.event_id.organizer_id.email}
+
Please contact the organizer at ${object.event_id.organizer_id.email}
@@ -252,9 +252,9 @@ Add this event to your calendar - Google - Outlook - Yahoo + Google + Outlook + Yahoo @@ -271,7 +271,7 @@ -
This email was sent to ${object.email}
+
This email was sent to ${object.email}

Copyright © ${object.company_id.name}

diff --git a/addons/event_sale/wizard/event_edit_registration.py b/addons/event_sale/wizard/event_edit_registration.py index 5cf4a17969c..a82d45b00ef 100644 --- a/addons/event_sale/wizard/event_edit_registration.py +++ b/addons/event_sale/wizard/event_edit_registration.py @@ -53,6 +53,9 @@ class RegistrationEditor(models.TransientModel): registration_line.registration_id.write(values) else: self.env['event.registration'].create(values) + if self.env.context.get('active_model') == 'sale.order': + for order in self.env['sale.order'].browse(self.env.context.get('active_ids', [])): + order.order_line._update_registrations(confirm=True) return {'type': 'ir.actions.act_window_close'} diff --git a/addons/fleet/static/description/index.html b/addons/fleet/static/description/index.html index 14cf7fbe59d..0d43777aeca 100644 --- a/addons/fleet/static/description/index.html +++ b/addons/fleet/static/description/index.html @@ -1,6 +1,6 @@
-

Odoo Fleet

+

Odoo Fleet

Manage your vehicles, contracts, costs, insurances and assignments

@@ -12,7 +12,7 @@
-

Fleet management made easy

+

Fleet management made easy

@@ -26,7 +26,7 @@
-

Manage leasing and all other contracts

+

Manage leasing and all other contracts

Supervise all contracts for your vehicles and receive a warning email when contracts reach their expiration date. Several visual tools are put in place to ensure that you'll remember to renew (or end) your contract. Organize the services around the vehicles and communicate with the qualified service providers; manage invoices and notes. Set up a vehicle policy within your company, as well as an insurance policy in order to manage your fleet in the most efficient way. @@ -40,7 +40,7 @@

-

Monitor all costs at once

+

Monitor all costs at once

@@ -54,7 +54,7 @@
-

Analysis and reporting

+

Analysis and reporting

Show all costs associated with a given vehicle or with a type of service. Compare different types of costs (which vehicles cost the most; which services have been performed on which vehicles etc.) by using the reporting tool. Get really helpful insights about the effective return of each vehicle in order to improve your fleet investments. diff --git a/addons/hr/static/description/index.html b/addons/hr/static/description/index.html index ed11a0bf109..7ab62ba0a56 100644 --- a/addons/hr/static/description/index.html +++ b/addons/hr/static/description/index.html @@ -1,6 +1,6 @@

-

Modern open source HR solution

+

Modern open source HR solution

Manage the most important asset in your company: People

@@ -12,7 +12,7 @@
-

Successfully manage your employees

+

Successfully manage your employees

Centralize all your HR information

@@ -27,7 +27,7 @@
-

Boost Engagement With Social Tools

+

Boost Engagement With Social Tools

Improve communication between employees and motivate them through rewards

diff --git a/addons/hr_attendance/__manifest__.py b/addons/hr_attendance/__manifest__.py index 6a8024282b8..c3228573d55 100644 --- a/addons/hr_attendance/__manifest__.py +++ b/addons/hr_attendance/__manifest__.py @@ -35,4 +35,5 @@ actions(Check in/Check out) performed by them. 'qweb': [ "static/src/xml/attendance.xml", ], + 'application': True, } diff --git a/addons/hr_attendance/models/hr_employee.py b/addons/hr_attendance/models/hr_employee.py index 386daa9907b..7fcd8ee8ff4 100644 --- a/addons/hr_attendance/models/hr_employee.py +++ b/addons/hr_attendance/models/hr_employee.py @@ -21,13 +21,13 @@ class HrEmployee(models.Model): return barcode barcode = fields.Char(string="Badge ID", help="ID used for employee identification.", default=_default_random_barcode, copy=False) - pin = fields.Char(string="PIN", default=_default_random_pin, help="PIN used for Check In/Out in Attendance.", copy=False) + pin = fields.Char(string="PIN", default=_default_random_pin, help="PIN used to Check In/Out in Kiosk Mode (if enabled in Configuration).", copy=False) attendance_ids = fields.One2many('hr.attendance', 'employee_id', help='list of attendances for the employee') last_attendance_id = fields.Many2one('hr.attendance', compute='_compute_last_attendance_id') attendance_state = fields.Selection(string="Attendance", compute='_compute_attendance_state', selection=[('checked_out', "Checked out"), ('checked_in', "Checked in")]) manual_attendance = fields.Boolean(string='Manual Attendance', compute='_compute_manual_attendance', inverse='_inverse_manual_attendance', - help='The employee will have access to the "My Attendances" menu to perform his check in and out from his session') + help='The employee will have access to the "My Attendances" menu to check in and out from his session') _sql_constraints = [('barcode_uniq', 'unique (barcode)', "The Badge ID must be unique, this one is already assigned to another employee.")] @@ -38,7 +38,7 @@ class HrEmployee(models.Model): @api.multi def _inverse_manual_attendance(self): - manual_attendance_group = self.env['res.groups'].search([('name', '=', 'Manual Attendances')]) + manual_attendance_group = self.env.ref('hr.group_hr_attendance') for employee in self: if employee.user_id: if employee.manual_attendance: @@ -53,8 +53,6 @@ class HrEmployee(models.Model): @api.depends('last_attendance_id.check_in', 'last_attendance_id.check_out', 'last_attendance_id') def _compute_attendance_state(self): - """compute employee state, last check in and last check out - """ for employee in self: employee.attendance_state = employee.last_attendance_id and not employee.last_attendance_id.check_out and 'checked_in' or 'checked_out' @@ -66,11 +64,11 @@ class HrEmployee(models.Model): @api.model def attendance_scan(self, barcode): - """ Receive a barcode scanned from the main menu and change the attendances of corresponding employee. + """ Receive a barcode scanned from the Kiosk Mode and change the attendances of corresponding employee. Returns either an action or a warning. """ employee = self.search([('barcode', '=', barcode)], limit=1) - return employee and employee.attendance_action('hr_attendance.hr_attendance_action_main_menu') or \ + return employee and employee.attendance_action('hr_attendance.hr_attendance_action_kiosk_mode') or \ {'warning': _('No employee corresponding to barcode %(barcode)s') % {'barcode': barcode}} @api.multi @@ -85,10 +83,10 @@ class HrEmployee(models.Model): def attendance_action(self, next_action): """ Changes the attendance of the employee. Returns an action to the check in/out message, - next_action defines which menu the check in/out message should return to. + next_action defines which menu the check in/out message should return to. ("My Attendances" or "Kiosk Mode") """ self.ensure_one() - action_message = self.env.ref('hr_attendance.hr_attendance_action_message').read()[0] + action_message = self.env.ref('hr_attendance.hr_attendance_action_greeting_message').read()[0] action_message['previous_attendance_change_date'] = self.last_attendance_id and (self.last_attendance_id.check_out or self.last_attendance_id.check_in) or False if action_message['previous_attendance_change_date']: action_message['previous_attendance_change_date'] = \ @@ -113,11 +111,7 @@ class HrEmployee(models.Model): raise exceptions.UserError(_('Cannot perform check in or check out on multiple employees.')) action_date = fields.Datetime.now() - action = 'check_in' - if self.attendance_state == 'checked_in': - action = 'check_out' - - if action == 'check_in': + if self.attendance_state != 'checked_in': vals = { 'employee_id': self.id, 'check_in': action_date, diff --git a/addons/hr_attendance/models/res_config.py b/addons/hr_attendance/models/res_config.py index 464b643ca02..d2f06ce6cb5 100644 --- a/addons/hr_attendance/models/res_config.py +++ b/addons/hr_attendance/models/res_config.py @@ -7,6 +7,6 @@ from odoo import fields, models class BaseConfigSettings(models.TransientModel): _inherit = 'base.config.settings' - group_attendance_use_pin = fields.Selection([(0, 'Employees do not need to enter their PIN to check in manually in the "Company Screen".'), - (1, 'Employees must enter their PIN to check in manually in the "Company Screen".')], + group_attendance_use_pin = fields.Selection([(0, 'Employees do not need to enter their PIN to check in manually in the "Kiosk Mode".'), + (1, 'Employees must enter their PIN to check in manually in the "Kiosk Mode".')], string='Employee PIN', help='Enable or disable employee PIN identification at check in', implied_group="hr_attendance.group_hr_attendance_use_pin") diff --git a/addons/hr_attendance/security/hr_attendance_security.xml b/addons/hr_attendance/security/hr_attendance_security.xml index 559df28e799..76a67019658 100644 --- a/addons/hr_attendance/security/hr_attendance_security.xml +++ b/addons/hr_attendance/security/hr_attendance_security.xml @@ -27,7 +27,8 @@ - Manual Attendances + Manual Attendance + The user will gain access to the human resources attendance menu, enabling him to manage his own attendance. diff --git a/addons/hr_attendance/static/src/js/employee_kanban_view_handler.js b/addons/hr_attendance/static/src/js/employee_kanban_view_handler.js index b3810f05a2d..225077b0794 100644 --- a/addons/hr_attendance/static/src/js/employee_kanban_view_handler.js +++ b/addons/hr_attendance/static/src/js/employee_kanban_view_handler.js @@ -11,7 +11,7 @@ KanbanRecord.include({ var action = { type: 'ir.actions.client', name: 'Confirm', - tag: 'hr_attendance_main_menu_confirm', + tag: 'hr_attendance_kiosk_confirm', employee_id: this.record.id.raw_value, employee_name: this.record.name.raw_value, employee_state: this.record.attendance_state.raw_value, diff --git a/addons/hr_attendance/static/src/js/message.js b/addons/hr_attendance/static/src/js/greeting_message.js similarity index 93% rename from addons/hr_attendance/static/src/js/message.js rename to addons/hr_attendance/static/src/js/greeting_message.js index 20cb767692f..113012dbb18 100644 --- a/addons/hr_attendance/static/src/js/message.js +++ b/addons/hr_attendance/static/src/js/greeting_message.js @@ -1,4 +1,4 @@ -odoo.define('hr_attendance.Message', function (require) { +odoo.define('hr_attendance.greeting_message', function (require) { "use strict"; var core = require('web.core'); @@ -8,8 +8,8 @@ var Widget = require('web.Widget'); var _t = core._t; -var Message = Widget.extend({ - template: 'HrAttendanceMessage', +var GreetingMessage = Widget.extend({ + template: 'HrAttendanceGreetingMessage', events: { "click .o_hr_attendance_button_dismiss": function () { this.do_action(this.next_action, {clear_breadcrumbs: true}); }, @@ -18,11 +18,11 @@ var Message = Widget.extend({ init: function(parent, action) { this._super.apply(this, arguments); var self = this; - var default_action = { type: "ir.actions.client", name: 'Attendances', tag: 'hr_attendance_my_main_menu', params: {} }; + var default_action = { type: "ir.actions.client", name: 'Attendances', tag: 'hr_attendance_my_attendances', params: {} }; if(!action.attendance){ self.session.user_has_group('hr_attendance.group_hr_attendance_user').then(function(has_group){ if(has_group){ - self.next_action = { type: "ir.actions.client", name: 'Attendances', tag: 'hr_attendance_main_menu', target: 'fullscreen', params: {} }; + self.next_action = { type: "ir.actions.client", name: 'Attendances', tag: 'hr_attendance_kiosk_mode', target: 'fullscreen', params: {} }; } }); return; @@ -124,8 +124,8 @@ var Message = Widget.extend({ }, }); -core.action_registry.add('hr_attendance_message', Message); +core.action_registry.add('hr_attendance_greeting_message', GreetingMessage); -return Message; +return GreetingMessage; }); diff --git a/addons/hr_attendance/static/src/js/main_menu_confirm.js b/addons/hr_attendance/static/src/js/kiosk_confirm.js similarity index 92% rename from addons/hr_attendance/static/src/js/main_menu_confirm.js rename to addons/hr_attendance/static/src/js/kiosk_confirm.js index 0f54d607732..60e75b73328 100644 --- a/addons/hr_attendance/static/src/js/main_menu_confirm.js +++ b/addons/hr_attendance/static/src/js/kiosk_confirm.js @@ -1,4 +1,4 @@ -odoo.define('hr_attendance.MainMenuConfirm', function (require) { +odoo.define('hr_attendance.kiosk_confirm', function (require) { "use strict"; var core = require('web.core'); @@ -9,7 +9,7 @@ var QWeb = core.qweb; var _t = core._t; -var MainMenuConfirm = Widget.extend({ +var KioskConfirm = Widget.extend({ events: { "click .o_hr_attendance_back_button": function () { this.do_action(this.next_action, {clear_breadcrumbs: true}); }, "click .o_hr_attendance_sign_in_out_icon": function () { @@ -55,7 +55,7 @@ var MainMenuConfirm = Widget.extend({ init: function (parent, action) { this._super.apply(this, arguments); - this.next_action = { type: "ir.actions.client", name: "Attendances", tag: 'hr_attendance_main_menu', target: "fullscreen", params: {} }; + this.next_action = { type: "ir.actions.client", name: "Attendances", tag: 'hr_attendance_kiosk_mode', target: "fullscreen", params: {} }; this.employee_id = action.employee_id; this.employee_name = action.employee_name; this.employee_state = action.employee_state; @@ -66,7 +66,7 @@ var MainMenuConfirm = Widget.extend({ var self = this; self.session.user_has_group('hr_attendance.group_hr_attendance_use_pin').then(function(has_group){ self.use_pin = has_group; - self.$el.html(QWeb.render("HrAttendanceMainMenuConfirm", {widget: self})); + self.$el.html(QWeb.render("HrAttendanceKioskConfirm", {widget: self})); self.start_clock(); }); return self._super.apply(this, arguments); @@ -84,8 +84,8 @@ var MainMenuConfirm = Widget.extend({ }, }); -core.action_registry.add('hr_attendance_main_menu_confirm', MainMenuConfirm); +core.action_registry.add('hr_attendance_kiosk_confirm', KioskConfirm); -return MainMenuConfirm; +return KioskConfirm; }); diff --git a/addons/hr_attendance/static/src/js/main_menu.js b/addons/hr_attendance/static/src/js/kiosk_mode.js similarity index 89% rename from addons/hr_attendance/static/src/js/main_menu.js rename to addons/hr_attendance/static/src/js/kiosk_mode.js index aa6d6682381..51c78bd34b4 100644 --- a/addons/hr_attendance/static/src/js/main_menu.js +++ b/addons/hr_attendance/static/src/js/kiosk_mode.js @@ -1,4 +1,4 @@ -odoo.define('hr_attendance.MainMenu', function (require) { +odoo.define('hr_attendance.kiosk_mode', function (require) { "use strict"; var core = require('web.core'); @@ -11,7 +11,7 @@ var QWeb = core.qweb; var _t = core._t; -var MainMenu = Widget.extend(BarcodeHandlerMixin, { +var KioskMode = Widget.extend(BarcodeHandlerMixin, { events: { "click .o_hr_attendance_button_employees": function(){ this.do_action('hr_attendance.hr_employee_attendance_action_kanban'); }, }, @@ -33,7 +33,7 @@ var MainMenu = Widget.extend(BarcodeHandlerMixin, { .then(function (companies){ self.company_name = companies[0].name; self.company_image_url = self.session.url('/web/image', {model: 'res.company', id: self.session.company_id, field: 'logo',}) - self.$el.html(QWeb.render("HrAttendanceMainMenu", {widget: self})); + self.$el.html(QWeb.render("HrAttendanceKioskMode", {widget: self})); self.start_clock(); }); return self._super.apply(this, arguments); @@ -64,8 +64,8 @@ var MainMenu = Widget.extend(BarcodeHandlerMixin, { }, }); -core.action_registry.add('hr_attendance_main_menu', MainMenu); +core.action_registry.add('hr_attendance_kiosk_mode', KioskMode); -return MainMenu; +return KioskMode; }); diff --git a/addons/hr_attendance/static/src/js/my_main_menu.js b/addons/hr_attendance/static/src/js/my_attendances.js similarity index 85% rename from addons/hr_attendance/static/src/js/my_main_menu.js rename to addons/hr_attendance/static/src/js/my_attendances.js index 3d7d4fb9ed2..beed3c441e6 100644 --- a/addons/hr_attendance/static/src/js/my_main_menu.js +++ b/addons/hr_attendance/static/src/js/my_attendances.js @@ -1,4 +1,4 @@ -odoo.define('hr_attendance.MyMainMenu', function (require) { +odoo.define('hr_attendance.my_attendances', function (require) { "use strict"; var core = require('web.core'); @@ -9,7 +9,7 @@ var QWeb = core.qweb; var _t = core._t; -var MyMainMenu = Widget.extend({ +var MyAttendances = Widget.extend({ events: { "click .o_hr_attendance_sign_in_out_icon": function() { this.$('.o_hr_attendance_sign_in_out_icon').attr("disabled", "disabled"); @@ -39,7 +39,7 @@ var MyMainMenu = Widget.extend({ update_attendance: function () { var self = this; var hr_employee = new Model('hr.employee'); - hr_employee.call('attendance_manual', [[self.employee.id], 'hr_attendance.hr_attendance_action_my_main_menu']) + hr_employee.call('attendance_manual', [[self.employee.id], 'hr_attendance.hr_attendance_action_my_attendances']) .then(function(result) { if (result.action) { self.do_action(result.action); @@ -50,8 +50,8 @@ var MyMainMenu = Widget.extend({ }, }); -core.action_registry.add('hr_attendance_my_main_menu', MyMainMenu); +core.action_registry.add('hr_attendance_my_attendances', MyAttendances); -return MyMainMenu; +return MyAttendances; }); diff --git a/addons/hr_attendance/static/src/less/hr_attendance.less b/addons/hr_attendance/static/src/less/hr_attendance.less index b2b56bbaa0b..12a356159d4 100644 --- a/addons/hr_attendance/static/src/less/hr_attendance.less +++ b/addons/hr_attendance/static/src/less/hr_attendance.less @@ -57,7 +57,7 @@ margin: 5px 0px 5px 0px; } -.o_hr_attendance_main_menu_container { +.o_hr_attendance_kiosk_mode_container { .o-flex-display(); .o-flex-flow(column, nowrap); .o-justify-content(center); @@ -80,7 +80,7 @@ .o_hr_attendance_random_message { font-style: italic; } -.o_hr_attendance_main_menu { +.o_hr_attendance_kiosk_mode { width: 100%; text-align: center; position: relative; diff --git a/addons/hr_attendance/static/src/xml/attendance.xml b/addons/hr_attendance/static/src/xml/attendance.xml index 420dc1b4988..8f864341a1f 100644 --- a/addons/hr_attendance/static/src/xml/attendance.xml +++ b/addons/hr_attendance/static/src/xml/attendance.xml @@ -5,9 +5,9 @@
- -
-
+ +
+

Welcome to

Company Logo

Scan your badge

@@ -19,8 +19,8 @@ -
-
+
+

Welcome

@@ -39,9 +39,9 @@
- -
-
+ +
+

Welcome

@@ -89,7 +89,7 @@
- +
diff --git a/addons/hr_attendance/views/hr_attendance_view.xml b/addons/hr_attendance/views/hr_attendance_view.xml index 3555b415080..dae22297373 100644 --- a/addons/hr_attendance/views/hr_attendance_view.xml +++ b/addons/hr_attendance/views/hr_attendance_view.xml @@ -86,7 +86,10 @@ tree,form {"search_default_today":1} - The Time Tracking functionality aims to manage employee attendances from Check in/Check out actions. You can also link this feature to an attendance device using Odoo's web service features. + +

The attendance records of your employees will be displayed here.

+

Please make sure you're using the correct filter if you expected to see any.

+
@@ -109,28 +112,28 @@ - + Attendances - hr_attendance_main_menu + hr_attendance_kiosk_mode fullscreen - + Attendance - hr_attendance_my_main_menu + hr_attendance_my_attendances main - + Message - hr_attendance_message + hr_attendance_greeting_message - + @@ -138,7 +141,7 @@ - + diff --git a/addons/hr_attendance/views/hr_employee_view.xml b/addons/hr_attendance/views/hr_employee_view.xml index e70e347cd2f..085b8c32350 100644 --- a/addons/hr_attendance/views/hr_employee_view.xml +++ b/addons/hr_attendance/views/hr_employee_view.xml @@ -82,6 +82,14 @@ hr.employee kanban + +

+ Create a few employees to be able to select an employee here and perform his check in / check out. +

+

+ To create employees go to the Employees menu. +

+
\ No newline at end of file diff --git a/addons/hr_attendance/views/web_asset_backend_template.xml b/addons/hr_attendance/views/web_asset_backend_template.xml index 1041d410e1c..375a5e5c43a 100644 --- a/addons/hr_attendance/views/web_asset_backend_template.xml +++ b/addons/hr_attendance/views/web_asset_backend_template.xml @@ -4,10 +4,10 @@ - - - - + + + + diff --git a/addons/hr_expense/models/hr_expense.py b/addons/hr_expense/models/hr_expense.py index a6b1364308d..f8dc372f39e 100644 --- a/addons/hr_expense/models/hr_expense.py +++ b/addons/hr_expense/models/hr_expense.py @@ -339,7 +339,8 @@ class HrExpense(models.Model): 'product_id': product.id, 'product_uom_id': product.uom_id.id, 'quantity': 1, - 'unit_amount': price + 'unit_amount': price, + 'company_id': employee.company_id.id, }) return super(HrExpense, self).message_new(msg_dict, custom_values) @@ -348,7 +349,7 @@ class HrExpenseSheet(models.Model): _name = "hr.expense.sheet" _inherit = ['mail.thread', 'ir.needaction_mixin'] _description = "Expense Report" - _order = "accounting_date desc" + _order = "accounting_date desc, id desc" name = fields.Char(string='Expense Report Summary', required=True) expense_line_ids = fields.One2many('hr.expense', 'sheet_id', string='Expense Lines', states={'done': [('readonly', True)], 'post': [('readonly', True)]}, copy=False) diff --git a/addons/hr_expense/static/description/index.html b/addons/hr_expense/static/description/index.html index ab82ebff094..ef90d6e01fc 100644 --- a/addons/hr_expense/static/description/index.html +++ b/addons/hr_expense/static/description/index.html @@ -1,6 +1,6 @@
-

Online Expenses Management

+

Online Expenses Management

Easily Manage Employees' Expenses

@@ -12,7 +12,7 @@
-

Save time on expense records

+

Save time on expense records

Everything at one place

Manage your employees' daily expenses. Whether it's travel expenses or any other costs, access all your employees' fee notes and complete, validate or refuse them. No need to download any specialized software or program to manage expenses - everything can be done directly in the app! @@ -25,7 +25,7 @@

-

Stop losing receipts

+

Stop losing receipts

Upload all receipts within expense records

@@ -40,7 +40,7 @@

-

Manage expenses per team

+

Manage expenses per team

Have a clear overview of a team's spendings

As a manager, follow expenses recorded by a full team to keep an eye on the costs and make sure to keep to the target and budget. @@ -53,7 +53,7 @@

-

Share the workload between departments

+

Share the workload between departments

Get everyone involved to save time

diff --git a/addons/hr_holidays/__init__.py b/addons/hr_holidays/__init__.py index d84fb182e8f..859f7ca8e08 100644 --- a/addons/hr_holidays/__init__.py +++ b/addons/hr_holidays/__init__.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +import controllers import models import report import wizard diff --git a/addons/hr_holidays/controllers/__init__.py b/addons/hr_holidays/controllers/__init__.py new file mode 100644 index 00000000000..12d2d759f34 --- /dev/null +++ b/addons/hr_holidays/controllers/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -* +import main diff --git a/addons/hr_holidays/controllers/main.py b/addons/hr_holidays/controllers/main.py new file mode 100644 index 00000000000..cac9dbc1f52 --- /dev/null +++ b/addons/hr_holidays/controllers/main.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo.addons.mail.controllers.main import MailController +from odoo import http + + +class HrHolidaysController(http.Controller): + + @http.route('/hr_holidays/validate', type='http', auth='user', methods=['GET']) + def hr_holidays_validate(self, res_id, token): + comparison, record, redirect = MailController._check_token_and_record_or_redirect('hr.holidays', int(res_id), token) + if comparison and record: + try: + record.signal_workflow('validate') + except Exception: + return MailController._redirect_to_messaging() + return redirect + + @http.route('/hr_holidays/refuse', type='http', auth='user', methods=['GET']) + def hr_holidays_refuse(self, res_id, token): + comparison, record, redirect = MailController._check_token_and_record_or_redirect('hr.holidays', int(res_id), token) + if comparison and record: + try: + record.signal_workflow('refuse') + except Exception: + return MailController._redirect_to_messaging() + return redirect diff --git a/addons/hr_holidays/models/hr_holidays.py b/addons/hr_holidays/models/hr_holidays.py index dee086da682..dc515716ca8 100644 --- a/addons/hr_holidays/models/hr_holidays.py +++ b/addons/hr_holidays/models/hr_holidays.py @@ -560,10 +560,10 @@ class Holidays(models.Model): self.ensure_one() if self.state == 'confirm': - app_action = self._notification_link_helper('method', method='action_validate') + app_action = self._notification_link_helper('controller', controller='/hr_holidays/validate') hr_actions = [{'url': app_action, 'title': _('Approve')}] if self.state in ['confirm', 'validate', 'validate1']: - ref_action = self._notification_link_helper('method', method='action_refuse') + ref_action = self._notification_link_helper('controller', controller='/hr_holidays/refuse') hr_actions = [{'url': ref_action, 'title': _('Refuse')}] new_group = ( diff --git a/addons/hr_holidays/static/description/index.html b/addons/hr_holidays/static/description/index.html index 25ca1dc4a95..88d8279db25 100644 --- a/addons/hr_holidays/static/description/index.html +++ b/addons/hr_holidays/static/description/index.html @@ -1,6 +1,6 @@
-

Leaves management for all

+

Leaves management for all

Manage employee vacations & absence

@@ -12,7 +12,7 @@
-

Manage employee leaves

+

Manage employee leaves

Keep track of all your employees vacations

@@ -30,7 +30,7 @@

-

Approve or refuse leave requests

+

Approve or refuse leave requests

Handle all requests from your employees

@@ -45,7 +45,7 @@
-

Get reports to plan ahead

+

Get reports to plan ahead

Simple reporting tool

diff --git a/addons/hr_recruitment/static/description/index.html b/addons/hr_recruitment/static/description/index.html index 82b272ebb60..0805526956f 100644 --- a/addons/hr_recruitment/static/description/index.html +++ b/addons/hr_recruitment/static/description/index.html @@ -1,6 +1,6 @@

-

Hiring Processes Made Easy

+

Hiring Processes Made Easy

From the sourcing to the contract, handle your recruitment process easily

@@ -12,7 +12,7 @@
-

Manage your hiring process

+

Manage your hiring process

Organize your vacancies job applications

@@ -27,7 +27,7 @@
-

Track job offers straight from the app

+

Track job offers straight from the app

See which channel drives most applications and gather all of them in Odoo.

@@ -57,7 +57,7 @@
-

Customize your recruitment process

+

Customize your recruitment process

Define your own stages and interviewers

@@ -79,7 +79,7 @@
-

Simplify application management

+

Simplify application management

Index resumes, track applicants, search profiles

@@ -94,7 +94,7 @@

-

Integrated Surveys

+

Integrated Surveys

Define your own online/offline surveys

diff --git a/addons/hr_timesheet_sheet/static/description/index.html b/addons/hr_timesheet_sheet/static/description/index.html index e18e7ec8fdd..552d8ebefc7 100644 --- a/addons/hr_timesheet_sheet/static/description/index.html +++ b/addons/hr_timesheet_sheet/static/description/index.html @@ -1,6 +1,6 @@
-

Track time, Forecast productivity

+

Track time, Forecast productivity

Get things done as fast as you can think.

@@ -12,7 +12,7 @@
-

It supports your own work flow

+

It supports your own work flow

Getting Things Done. Timer. Weekly Timesheet.

@@ -45,7 +45,7 @@
-

Enjoy Timesheet anywhere. Even offline.

+

Enjoy Timesheet anywhere. Even offline.

The mobile app that adapts to your work

Whether you 're a small business, a freelance or a professional managing employees from different locations, you need your apps to be as mobile as you are. Log in and track your time through any device. diff --git a/addons/im_livechat/static/description/index.html b/addons/im_livechat/static/description/index.html index 9743881bd77..75b50edc054 100644 --- a/addons/im_livechat/static/description/index.html +++ b/addons/im_livechat/static/description/index.html @@ -1,6 +1,6 @@

-

Answer your customers questions live

+

Answer your customers questions live

Engage with visitors, deliver real time support to customers

@@ -12,7 +12,7 @@
-

Do it live!

+

Do it live!

Convert more visitors into leads and customers

@@ -39,7 +39,7 @@
-

Select your audience

+

Select your audience

Provide direct chat to a specified target

@@ -53,7 +53,7 @@
-

Accessible from anywhere

+

Accessible from anywhere

Chat from within any module

@@ -68,7 +68,7 @@

-

Improve your level of service

+

Improve your level of service

Make sure to keep customers satisfied

@@ -86,7 +86,7 @@
-

Answer more quickly with templates

+

Answer more quickly with templates

Use pre-written answers to be faster

diff --git a/addons/l10n_ar/migrations/2.0/post-migrate_tags_on_taxes.py b/addons/l10n_ar/migrations/9.0.2.0/post-migrate_tags_on_taxes.py similarity index 100% rename from addons/l10n_ar/migrations/2.0/post-migrate_tags_on_taxes.py rename to addons/l10n_ar/migrations/9.0.2.0/post-migrate_tags_on_taxes.py diff --git a/addons/l10n_at/migrations/2.0/post-migrate_tags_on_taxes.py b/addons/l10n_at/migrations/9.0.2.0/post-migrate_tags_on_taxes.py similarity index 100% rename from addons/l10n_at/migrations/2.0/post-migrate_tags_on_taxes.py rename to addons/l10n_at/migrations/9.0.2.0/post-migrate_tags_on_taxes.py diff --git a/addons/l10n_at/migrations/2.0/pre-set_tags_and_taxes_updatable.py b/addons/l10n_at/migrations/9.0.2.0/pre-set_tags_and_taxes_updatable.py similarity index 100% rename from addons/l10n_at/migrations/2.0/pre-set_tags_and_taxes_updatable.py rename to addons/l10n_at/migrations/9.0.2.0/pre-set_tags_and_taxes_updatable.py diff --git a/addons/l10n_bo/migrations/2.0/post-migrate_tags_on_taxes.py b/addons/l10n_bo/migrations/9.0.2.0/post-migrate_tags_on_taxes.py similarity index 100% rename from addons/l10n_bo/migrations/2.0/post-migrate_tags_on_taxes.py rename to addons/l10n_bo/migrations/9.0.2.0/post-migrate_tags_on_taxes.py diff --git a/addons/l10n_br/migrations/1.0/post-migrate_tags_on_taxes.py b/addons/l10n_br/migrations/9.0.1.0/post-migrate_tags_on_taxes.py similarity index 100% rename from addons/l10n_br/migrations/1.0/post-migrate_tags_on_taxes.py rename to addons/l10n_br/migrations/9.0.1.0/post-migrate_tags_on_taxes.py diff --git a/addons/l10n_ch/migrations/9.0/post-migrate_tags_on_taxes.py b/addons/l10n_ch/migrations/9.0.9.0/post-migrate_tags_on_taxes.py similarity index 100% rename from addons/l10n_ch/migrations/9.0/post-migrate_tags_on_taxes.py rename to addons/l10n_ch/migrations/9.0.9.0/post-migrate_tags_on_taxes.py diff --git a/addons/l10n_ch/migrations/9.0/pre-set_tags_and_taxes_updatable.py b/addons/l10n_ch/migrations/9.0.9.0/pre-set_tags_and_taxes_updatable.py similarity index 100% rename from addons/l10n_ch/migrations/9.0/pre-set_tags_and_taxes_updatable.py rename to addons/l10n_ch/migrations/9.0.9.0/pre-set_tags_and_taxes_updatable.py diff --git a/addons/l10n_cl/migrations/2.0/post-migrate_tags_on_taxes.py b/addons/l10n_cl/migrations/9.0.2.0/post-migrate_tags_on_taxes.py similarity index 100% rename from addons/l10n_cl/migrations/2.0/post-migrate_tags_on_taxes.py rename to addons/l10n_cl/migrations/9.0.2.0/post-migrate_tags_on_taxes.py diff --git a/addons/l10n_cl/migrations/2.0/pre-set_tags_and_taxes_updatable.py b/addons/l10n_cl/migrations/9.0.2.0/pre-set_tags_and_taxes_updatable.py similarity index 100% rename from addons/l10n_cl/migrations/2.0/pre-set_tags_and_taxes_updatable.py rename to addons/l10n_cl/migrations/9.0.2.0/pre-set_tags_and_taxes_updatable.py diff --git a/addons/l10n_de_skr03/migrations/2.0/post-migrate_tags_on_taxes.py b/addons/l10n_de_skr03/migrations/9.0.2.0/post-migrate_tags_on_taxes.py similarity index 100% rename from addons/l10n_de_skr03/migrations/2.0/post-migrate_tags_on_taxes.py rename to addons/l10n_de_skr03/migrations/9.0.2.0/post-migrate_tags_on_taxes.py diff --git a/addons/l10n_de_skr03/migrations/2.0/pre-set_tags_and_taxes_updatable.py b/addons/l10n_de_skr03/migrations/9.0.2.0/pre-set_tags_and_taxes_updatable.py similarity index 100% rename from addons/l10n_de_skr03/migrations/2.0/pre-set_tags_and_taxes_updatable.py rename to addons/l10n_de_skr03/migrations/9.0.2.0/pre-set_tags_and_taxes_updatable.py diff --git a/addons/l10n_de_skr04/migrations/2.0/post-migrate_tags_on_taxes.py b/addons/l10n_de_skr04/migrations/9.0.2.0/post-migrate_tags_on_taxes.py similarity index 100% rename from addons/l10n_de_skr04/migrations/2.0/post-migrate_tags_on_taxes.py rename to addons/l10n_de_skr04/migrations/9.0.2.0/post-migrate_tags_on_taxes.py diff --git a/addons/l10n_de_skr04/migrations/2.0/pre-set_tags_and_taxes_updatable.py b/addons/l10n_de_skr04/migrations/9.0.2.0/pre-set_tags_and_taxes_updatable.py similarity index 100% rename from addons/l10n_de_skr04/migrations/2.0/pre-set_tags_and_taxes_updatable.py rename to addons/l10n_de_skr04/migrations/9.0.2.0/pre-set_tags_and_taxes_updatable.py diff --git a/addons/l10n_et/migrations/2.0/post-migrate_tags_on_taxes.py b/addons/l10n_et/migrations/9.0.2.0/post-migrate_tags_on_taxes.py similarity index 100% rename from addons/l10n_et/migrations/2.0/post-migrate_tags_on_taxes.py rename to addons/l10n_et/migrations/9.0.2.0/post-migrate_tags_on_taxes.py diff --git a/addons/l10n_gr/migrations/1.0/post-migrate_tags_on_taxes.py b/addons/l10n_gr/migrations/9.0.1.0/post-migrate_tags_on_taxes.py similarity index 100% rename from addons/l10n_gr/migrations/1.0/post-migrate_tags_on_taxes.py rename to addons/l10n_gr/migrations/9.0.1.0/post-migrate_tags_on_taxes.py diff --git a/addons/l10n_hr/migrations/13.0/post-migrate_tags_on_taxes.py b/addons/l10n_hr/migrations/9.0.13.0/post-migrate_tags_on_taxes.py similarity index 100% rename from addons/l10n_hr/migrations/13.0/post-migrate_tags_on_taxes.py rename to addons/l10n_hr/migrations/9.0.13.0/post-migrate_tags_on_taxes.py diff --git a/addons/l10n_hr/migrations/13.0/pre-set_tags_and_taxes_updatable.py b/addons/l10n_hr/migrations/9.0.13.0/pre-set_tags_and_taxes_updatable.py similarity index 100% rename from addons/l10n_hr/migrations/13.0/pre-set_tags_and_taxes_updatable.py rename to addons/l10n_hr/migrations/9.0.13.0/pre-set_tags_and_taxes_updatable.py diff --git a/addons/l10n_hu/migrations/2.0/post-migrate_tags_on_taxes.py b/addons/l10n_hu/migrations/9.0.2.0/post-migrate_tags_on_taxes.py similarity index 100% rename from addons/l10n_hu/migrations/2.0/post-migrate_tags_on_taxes.py rename to addons/l10n_hu/migrations/9.0.2.0/post-migrate_tags_on_taxes.py diff --git a/addons/l10n_in/migrations/2.0/post-migrate_tags_on_taxes.py b/addons/l10n_in/migrations/9.0.2.0/post-migrate_tags_on_taxes.py similarity index 100% rename from addons/l10n_in/migrations/2.0/post-migrate_tags_on_taxes.py rename to addons/l10n_in/migrations/9.0.2.0/post-migrate_tags_on_taxes.py diff --git a/addons/l10n_in/migrations/2.0/pre-set_tags_and_taxes_updatable.py b/addons/l10n_in/migrations/9.0.2.0/pre-set_tags_and_taxes_updatable.py similarity index 100% rename from addons/l10n_in/migrations/2.0/pre-set_tags_and_taxes_updatable.py rename to addons/l10n_in/migrations/9.0.2.0/pre-set_tags_and_taxes_updatable.py diff --git a/addons/l10n_jp/migrations/2.0/post-migrate_tags_on_taxes.py b/addons/l10n_jp/migrations/9.0.2.0/post-migrate_tags_on_taxes.py similarity index 100% rename from addons/l10n_jp/migrations/2.0/post-migrate_tags_on_taxes.py rename to addons/l10n_jp/migrations/9.0.2.0/post-migrate_tags_on_taxes.py diff --git a/addons/l10n_nl/migrations/2.0/post-migrate_tags_on_taxes.py b/addons/l10n_nl/migrations/9.0.2.0/post-migrate_tags_on_taxes.py similarity index 100% rename from addons/l10n_nl/migrations/2.0/post-migrate_tags_on_taxes.py rename to addons/l10n_nl/migrations/9.0.2.0/post-migrate_tags_on_taxes.py diff --git a/addons/l10n_nl/migrations/2.0/pre-set_tags_and_taxes_updatable.py b/addons/l10n_nl/migrations/9.0.2.0/pre-set_tags_and_taxes_updatable.py similarity index 100% rename from addons/l10n_nl/migrations/2.0/pre-set_tags_and_taxes_updatable.py rename to addons/l10n_nl/migrations/9.0.2.0/pre-set_tags_and_taxes_updatable.py diff --git a/addons/mail/controllers/main.py b/addons/mail/controllers/main.py index e9afcb8fab3..068bd2eeadd 100644 --- a/addons/mail/controllers/main.py +++ b/addons/mail/controllers/main.py @@ -3,26 +3,96 @@ import base64 import json +import logging import psycopg2 import werkzeug from operator import itemgetter from werkzeug import url_encode -from odoo import api, http, registry, SUPERUSER_ID +from odoo import api, http, registry, SUPERUSER_ID, _ from odoo.addons.web.controllers.main import binary_content from odoo.exceptions import AccessError from odoo.http import request +from odoo.tools import consteq + +_logger = logging.getLogger(__name__) class MailController(http.Controller): _cp_path = '/mail' - def _redirect_to_messaging(self): + @classmethod + def _redirect_to_messaging(cls): messaging_action = request.env['mail.thread']._get_inbox_action_xml_id() url = '/web#%s' % url_encode({'action': messaging_action}) return werkzeug.utils.redirect(url) + @classmethod + def _check_token(cls, token): + base_link = request.httprequest.path + params = dict(request.params) + params.pop('token', '') + valid_token = request.env['mail.thread']._generate_notification_token(base_link, params) + return consteq(valid_token, str(token)) + + @classmethod + def _check_token_and_record_or_redirect(cls, model, res_id, token): + comparison = cls._check_token(token) + if not comparison: + _logger.warning(_('Invalid token in route %s') % request.httprequest.url) + return comparison, None, cls._redirect_to_messaging() + try: + record = request.env[model].browse(res_id).exists() + except Exception: + record = None + redirect = cls._redirect_to_messaging() + else: + redirect = cls._redirect_to_record(model, res_id) + return comparison, record, redirect + + @classmethod + def _redirect_to_record(cls, model, res_id): + uid = request.session.uid + + # no model / res_id, meaning no possible record -> redirect to login + if not model or not res_id or model not in request.env: + return cls._redirect_to_messaging() + + # find the access action using sudo to have the details about the access link + RecordModel = request.env[model] + record_sudo = RecordModel.sudo().browse(res_id).exists() + if not record_sudo: + # record does not seem to exist -> redirect to login + return cls._redirect_to_messaging() + record_action = record_sudo.get_access_action() + + # the record has an URL redirection: use it directly + if record_action['type'] == 'ir.actions.act_url': + return werkzeug.utils.redirect(record_action['url']) + # other choice: act_window (no support of anything else currently) + elif not record_action['type'] == 'ir.actions.act_window': + return cls._redirect_to_messaging() + + # the record has a window redirection: check access rights + if not RecordModel.sudo(uid).check_access_rights('read', raise_exception=False): + return cls._redirect_to_messaging() + try: + record_sudo.sudo(uid).check_access_rule('read') + except AccessError: + return cls._redirect_to_messaging() + + url_params = { + 'view_type': record_action['view_type'], + 'model': model, + 'id': res_id, + 'active_id': res_id, + 'view_id': record_sudo.get_formview_id(), + 'action': record_action.get('id'), + } + url = '/web?#%s' % url_encode(url_params) + return werkzeug.utils.redirect(url) + @http.route('/mail/receive', type='json', auth='none') def receive(self, req): """ End-point to receive mail from an external SMTP server. """ @@ -94,8 +164,6 @@ class MailController(http.Controller): - users without read access are redirected to the Messaging - not logged users are redirected to the login page """ - uid = request.session.uid - if message_id: try: message = request.env['mail.message'].sudo().browse(int(message_id)).exists() @@ -109,112 +177,48 @@ class MailController(http.Controller): elif res_id and isinstance(res_id, basestring): res_id = int(res_id) - # no model / res_id, meaning no possible record -> redirect to login - if not model or not res_id or model not in request.env: - return self._redirect_to_messaging() + return self._redirect_to_record(model, res_id) - # find the access action using sudo to have the details about the access link - RecordModel = request.env[model].sudo(uid) - record = RecordModel.browse(res_id).exists() - if not record: - # record does not seem to exist -> redirect to login - return self._redirect_to_messaging() - record_action = record.get_access_action() + @http.route('/mail/follow', type='http', auth='user', methods=['GET']) + def mail_action_follow(self, model, res_id, token=None): + comparison, record, redirect = self._check_token_and_record_or_redirect(model, int(res_id), token) + if comparison and record: + try: + record.sudo().message_subscribe_users() + except Exception: + return self._redirect_to_messaging() + return redirect - # only URL redirections or window actions supported currently - if not record_action['type'] in ('ir.actions.act_url', 'ir.actions.act_window'): - return self._redirect_to_messaging() - - # the record has an URL redirection: use it directly - if record_action['type'] == 'ir.actions.act_url': - return werkzeug.utils.redirect(record_action['url']) - - # the record has a window redirection: check access rights - if not RecordModel.check_access_rights('read', raise_exception=False): - return self._redirect_to_messaging() - try: - record.check_access_rule('read') - except AccessError: - return self._redirect_to_messaging() - - # at this point user can read the document so no issue with get_formview_id - query = {} - url_params = { - 'view_type': record_action['view_type'], - 'model': model, - 'id': res_id, - 'active_id': res_id, - 'view_id': record.get_formview_id(), - 'action': record_action.get('id'), - } - url = '/web?%s#%s' % (url_encode(query), url_encode(url_params)) - return werkzeug.utils.redirect(url) - - @http.route('/mail/follow', type='http', auth='user') - def mail_action_follow(self, model, res_id): - if model not in request.env: - return self._redirect_to_messaging() - Model = request.env[model] - try: - Model.browse(res_id).message_subscribe_users() - except: - return self._redirect_to_messaging() - return werkzeug.utils.redirect('/mail/view?%s' % url_encode({'model': model, 'res_id': res_id})) - - @http.route('/mail/unfollow', type='http', auth='user') - def mail_action_unfollow(self, model, res_id): - if model not in request.env: - return self._redirect_to_messaging() - Model = request.env[model] - try: - Model.browse(res_id).sudo().message_unsubscribe_users([request.uid]) - except: - return self._redirect_to_messaging() - return werkzeug.utils.redirect('/mail/view?%s' % url_encode({'model': model, 'res_id': res_id})) + @http.route('/mail/unfollow', type='http', auth='user', methods=['GET']) + def mail_action_unfollow(self, model, res_id, token=None): + comparison, record, redirect = self._check_token_and_record_or_redirect(model, int(res_id), token) + if comparison and record: + try: + # TDE CHECKME: is sudo really necessary ? + record.sudo().message_unsubscribe_users([request.uid]) + except Exception: + return self._redirect_to_messaging() + return redirect @http.route('/mail/new', type='http', auth='user') - def mail_action_new(self, model, res_id, **kwargs): + def mail_action_new(self, model, res_id, action_id): if model not in request.env: return self._redirect_to_messaging() params = {'view_type': 'form', 'model': model} - if kwargs.get('action_id'): - params['action'] = kwargs['action_id'] + if action_id: + # Probably something to do + params['action'] = action_id return werkzeug.utils.redirect('/web?#%s' % url_encode(params)) - @http.route('/mail/method', type='http', auth='user') - def mail_action_method(self, model, res_id, method, **kwargs): - # only public methods / check exists - if method.strip().startswith('_') or model not in request.env: - return self._redirect_to_messaging() - Model = request.env[model] - try: - record = Model.browse(int(res_id)).exists() - getattr(record, method)(**json.loads(kwargs.get('params', {}))) - except: - return self._redirect_to_messaging() - return werkzeug.utils.redirect('/mail/view?%s' % url_encode({'model': model, 'res_id': res_id})) - - @http.route('/mail/assign', type='http', auth='user') - def mail_action_assign(self, model, res_id): - if model not in request.env: - return self._redirect_to_messaging() - Model = request.env[model] - try: - Model.browse(int(res_id)).exists().write({'user_id': request.uid}) - except: - return self._redirect_to_messaging() - return werkzeug.utils.redirect('/mail/view?%s' % url_encode({'model': model, 'res_id': res_id})) - - @http.route('/mail/workflow', type='http', auth='user') - def mail_action_workflow(self, model, res_id, signal, **kwargs): - if model not in request.env: - return self._redirect_to_messaging() - Model = request.env[model] - try: - Model.browse(int(res_id)).exists().signal_workflow(signal) - except: - return self._redirect_to_messaging() - return werkzeug.utils.redirect('/mail/view?%s' % url_encode({'model': model, 'res_id': res_id})) + @http.route('/mail/assign', type='http', auth='user', methods=['GET']) + def mail_action_assign(self, model, res_id, token=None): + comparison, record, redirect = self._check_token_and_record_or_redirect(model, int(res_id), token) + if comparison and record: + try: + record.write({'user_id': request.uid}) + except Exception: + return self._redirect_to_messaging() + return redirect @http.route('/mail///avatar/', type='http', auth='public') def avatar(self, res_model, res_id, partner_id): diff --git a/addons/mail/data/mail_data.xml b/addons/mail/data/mail_data.xml index c229ef0f54e..4ca940fecd1 100644 --- a/addons/mail/data/mail_data.xml +++ b/addons/mail/data/mail_data.xml @@ -82,25 +82,25 @@ % if ctx.get('has_button_access'): - ${ctx['button_access']['title']} + ${ctx['button_access']['title']} % endif % if ctx.get('has_button_follow'): % if ctx.get('has_button_access'): | % endif - ${ctx['button_follow']['title']} + ${ctx['button_follow']['title']} % elif ctx.get('has_button_unfollow'): % if ctx.get('has_button_access'): | % endif - ${ctx['button_unfollow']['title']} + ${ctx['button_unfollow']['title']} % endif % if ctx.get('actions'): % for action in ctx['actions']: | - ${action['title']} + ${action['title']} % endfor % endif @@ -138,14 +138,14 @@ ${object.body | safe}

Sent by % if ctx.get('website_url'): - + % endif ${ctx.get('company_name')} % if ctx.get('website_url'): % endif using - Odoo. + Odoo.

]]> diff --git a/addons/mail/doc/changelog.rst b/addons/mail/doc/changelog.rst deleted file mode 100644 index 5eee5f136a6..00000000000 --- a/addons/mail/doc/changelog.rst +++ /dev/null @@ -1,26 +0,0 @@ -.. _changelog: - -Changelog -========= - -`trunk (saas-2)` ----------------- - - - ``mass_mailing_campaign`` update - - - ``mail_mail`: moved ``reply_to`` computation from ``mail_mail`` to ``mail_message`` - where it belongs, as the field is located onto the ``mail_message`` model. - - ``mail_compose_message``: template rendering is now done in batch. Each template - is rendered for all res_ids, instead of all templates one id at a time. - - ``mail_thread``: to ease inheritance, processing of routes is now done in - message_route_process, called in message_route - - - added support of ``active_domain`` form context, coming from the list view. - When checking the header hook, the mass mailing will be done on all records - matching the ``active_domain``. - - added ``mail_server_id`` to mail_message, removing it from mail_mail. This allows - to set the preferred mail server to use for notifications emails, when using - templates. - - added ``_mail_post_access`` attribute that specifies the access right that - should have the user in order to post a new message on a given model. Values - are ``read`` (portal documents), ``write`` (default value), ``unlink`` or ``create``. diff --git a/addons/mail/doc/index.rst b/addons/mail/doc/index.rst deleted file mode 100644 index 033b1241af6..00000000000 --- a/addons/mail/doc/index.rst +++ /dev/null @@ -1,12 +0,0 @@ -Mail module documentation -========================= - -The Mail module holds all models and logic related to messages management. At low-level, it handles messages and offers an API to message queues for email sending. At an higher level, it provides the OpenChatter feature that is a thread management system inside OpenERP. A model that inherits from the mail module adds OpenChatter to its document. Its gives them the following capabilities : - - - messages management with a threaded design - - subscription mechanism tha allow users to follow/unfollow documents - - notifications mechanism; notifications are pushed to users to form a Wall page holding the latest pushed messages - -The mail module also comes with an email composition wizard, along with discussion groups. - -.. include:: index.rst.inc diff --git a/addons/mail/doc/index.rst.inc b/addons/mail/doc/index.rst.inc deleted file mode 100644 index 7f56666336b..00000000000 --- a/addons/mail/doc/index.rst.inc +++ /dev/null @@ -1,23 +0,0 @@ - -Mail Module documentation topics -''''''''''''''''''''''''''''''''' - -.. toctree:: - :maxdepth: 1 - - mail_message - mail_thread - mail_openchatter_howto - mail_needaction_howto - mail_partner - mail_state - mail_subtype - - -Changelog -''''''''' - -.. toctree:: - :maxdepth: 1 - - changelog.rst diff --git a/addons/mail/doc/mail_message.rst b/addons/mail/doc/mail_message.rst deleted file mode 100644 index de58b3bebf2..00000000000 --- a/addons/mail/doc/mail_message.rst +++ /dev/null @@ -1,111 +0,0 @@ -.. _mail_message: - -mail.message -============ - -Models -++++++ - -``mail.message`` is a class for holding the main attributes of a message object -(notification for system message, reciving email message or sent messages). It -could be reused as parent model for any database model, wall thread view, or -wizard screen that needs to hold a kind of message. - -All internal logic should be in a database-based model while this model -holds the basics of a message. For example, a wizard for writing emails -should inherit from this class. - - -.. versionchanged:: 7.0 - -ClientAction (ir.actions.client) -++++++++++++++++++++++++++++++++ - -.. code-block:: xml - - - Inbox - mail.wall - mail.message - - - - - - - ``tag`` : 'mail.wall', 'mail_thread' or 'mail.widget' - 'mail.wall' to have a display like the mail wall with threads, title, search view - (default value like a wall) - 'mail_thread' widget for field on standard view. (default value like a thread for - record, view on flat mode, no reply, no read/unread) - 'mail.widget' it's the root thread, used by 'mail.wall' and 'mail_thread' - - - ``help`` : Text HTML to display if there are no message - - ``context`` : insert 'default_model' and 'default_res_id' - - ``params`` : options for the widget - - ``domain`` : choose the domain of the messages - - ``truncate_limit`` : {Number} number of character to display before having a "show more" - link; note that the text will not be truncated if it does not have 110% of the parameter - - ``show_record_name`` : {Boolean} display the name and link of the related record - - ``show_reply_button`` : {Boolean} display the reply button - - ``show_read_unread_button`` : {Boolean} display the read/unread button - - ``display_indented_thread`` : {int [0,1]} number thread level to indented threads. - - ``show_compose_message`` : display the composer on top thread - - ``show_compact_message`` : display the compact message on the thread when the user clic - on this compact mode, the composer is open - - ``message_ids`` : {Array | False} List of ids to fetch by the root thread. If no value, - the root search the message by the domain - - ``compose_placeholder`` : Message to display on the textareaboxes. - - ``show_link`` : Display partner (authors, followers...) on link or not - - ``compose_as_todo`` : The root composer mark automatically the message as todo - - ``readonly`` : Read only mode, hide all action buttons and composer - -Fields -++++++ - - - ``type`` : usually 'email', 'comment', 'notification'. - Message type: email for external email message recieve, notification for system - message, comment for other messages such as user replies. - - ``subtype_id`` : - Subtype of the notification for system message. The users can followe a document - and choose the subtype of this document (eg: Create, Comment, Done). - You can defined new subtypes and choose his name, by agreement the id begin by "mt\_" on the model - "mail.message.subtype". - - ``partner_ids`` : - List of recipients, the recipients have this message in their personal mailboxe. - - ``notified_partner_ids`` : - Partners that have a notification pushing this message in their mailboxes. Usualy - it's an automatic system message for a model followed by users. - - ``notification_ids`` : - Technical field holding the message notifications. Use notified_partner_ids to access - notified partners. - - ``attachment_ids`` : - List of attachments - - ``parent_id`` : - It's the initial thread message. It's use for group message by thread in mailboxes. - - ``child_ids`` : - List of child message linked to the initial thread message. - - ``model`` : - Related Document Moded. It's use for group message by document. - - ``res_id`` : - Related Document ID. It's use for group message by document. - - ``record_name`` : - Functionnal field use to get the name of related document. - - ``vote_user_ids`` : - List of partner that vote/like this message. - - ``to_read`` : - Functional field to search for messages the current user has to read. The messages as - treat like a need action. When the user recive a message (to_read = True) this message - or action must be performed and mark done (to_read = False) - - ``favorite_user_ids`` : - Users that set this message in their favorites/todo list. - -Methods -+++++++ - - - ``message_read`` : - Value: ids, domain, message_unload_ids, thread_level, context, parent_id, limit - Return: List of dictinary of message. All message is sent with their parented messages and - sort by id. The messages that the user can read but not in his search, are group in - expandable messages. The expandable messages contain the domain to expand. - - ``check_access_rule`` : - Overwrite the initial message for this model. diff --git a/addons/mail/doc/mail_needaction_howto.rst b/addons/mail/doc/mail_needaction_howto.rst deleted file mode 100644 index 8cb41cee374..00000000000 --- a/addons/mail/doc/mail_needaction_howto.rst +++ /dev/null @@ -1,15 +0,0 @@ - -How to use the need action mechanism in my addon -================================================= - -Make your module inheriting from ir.needaction_mixin class - -Feature integration -++++++++++++++++++++ - -:: - - class my_module(osv.osv): - _name = "my.module" - _description = "My Module" - _inherit = ['ir.needaction_mixin'] diff --git a/addons/mail/doc/mail_openchatter_howto.rst b/addons/mail/doc/mail_openchatter_howto.rst deleted file mode 100644 index f8b4fd98934..00000000000 --- a/addons/mail/doc/mail_openchatter_howto.rst +++ /dev/null @@ -1,112 +0,0 @@ - -How to use OpenChatter in my addon -=================================== - -Running example -++++++++++++++++ - -A small my_task model will be used as example to explain how to use the -OpenChatter feature. Being simple, it has only the following fields: - -- a name -- a task responsible -- a related project - -:: - - class my_task(osv.osv): - _name = "my.task" - _description = "My Task" - _columns = { - 'name': fields.char('Name', required=True, size=64), - 'user_id':fields.many2one('res.users', string='Responsible', - ondelete='cascade', required=True, select=1), - 'project_id':fields.many2one('project.project', string='Related project', - ondelete='cascade', required=True, select=1), - } - - -Two-lines feature integration -++++++++++++++++++++++++++++++ - -Make your object inherit from :class:`mail.thread`:: - - class my_task(osv.osv): - _name = "my.task" - _description = "My Task" - # inherit from mail.thread allows the use of OpenChatter - _inherit = ['mail.thread'] - -Use the thread viewer widget inside your form view by using the mail_thread -widget on the message_ids field inherited from :class:`mail.thread`:: - - - My Task - my.task - 1 - -
- [...] - - -
-
- -Send notifications -+++++++++++++++++++ - -When sending a notification is required in your workflow or business logic, -use :meth:`mail.thread.message_post`. It will automatically take care of -subscriptions and notifications. - -Here is a small example of sending a notification when the ``do_something`` -method is called:: - - def do_something(self, cr, uid, ids, context=None): - self.do_something_send_note(cr, uid, ids, context=context) - return res - - def do_something_send_note(self, cr, uid, ids, context=None): - self.message_post( - cr, uid, ids, _('My subject'), - _("has received a notification and is happy for it."), - context=context) - -Notifications guidelines -+++++++++++++++++++++++++ - -- avoid unnecessary content, swamping users with irrelevant messages will lead - to them ignoring all messages -- use short sentences -- do not include the document name, this is done by the thread widget -- use a simple and clean style - - - html tags are supported: use or mainly - - put key word(s) in bold - - avoid fancy styles that will break the OpenERP look and feel -- create a separate method for sending your notification, use clear method - names allowing quickly spotting notification code e.g. name notification - methods by using the original method name postfixed by ``_send_note`` - (``do_something`` -> ``do_something_send_note``) - -Subscription management -++++++++++++++++++++++++ - -The default subscription behavior is the following: - -* Subscriptions are set up by creating a :class:`mail.followers`` entry -* If a user creates or updates a document, they automatically follow it. The - corresponding :class:`mail.followers` entry is created -* If a user explicitly cliks on the document's :guilabel:`Follow` button, - they follow the document. The corresponding :class:`mail.followers` entry - is created -* If a user explicitly clicks on the document's :guilabel:`Unfollow` button, - they stop following the document. The corresponding :class:`mail.followers` - entry is deleted - -You should not directly manipulate :class:`mail.followers` entry, if you need -to override the default subscription behavior you should override the relevant -:class:`mail.thread` methods. - -.. TODO: wtf are the relevant mail.thread methds? message_get_subscribers - has disappeared and nothing looks like a replacement diff --git a/addons/mail/doc/mail_partner.rst b/addons/mail/doc/mail_partner.rst deleted file mode 100644 index a99246395d7..00000000000 --- a/addons/mail/doc/mail_partner.rst +++ /dev/null @@ -1,10 +0,0 @@ -What is shown -============== - - for every module which are related to partner show apporopriate message in the partner view like opportunities, sale orders and invoices. - - -How it is done -=============== - - _inherit = 'mail.thread' - - - Override def message_load_ids(self, cr, uid, ids, limit=100, offset=0, domain=[], ascent=False, root_ids=[], context=None) search by the partner diff --git a/addons/mail/doc/mail_state.rst b/addons/mail/doc/mail_state.rst deleted file mode 100644 index cf1874ffca1..00000000000 --- a/addons/mail/doc/mail_state.rst +++ /dev/null @@ -1,28 +0,0 @@ -.. _mail_state: - -message_unread -============== - -``message_unread`` is a boolean field that states whether the document -has unread messages. In previous versions, some documents were going -back to ``pending`` state when receiving an email through the mail -gateway. Now the state related to messages differs from the state or -stage of the document itself. - -message_unread and need action mechanism -++++++++++++++++++++++++++++++++++++++++ - -The ``mail`` module introduces a default behavior for the need_action -mechanism [REF]. - -:: - - def get_needaction_user_ids(self, cr, uid, ids, context=None): - """ Returns the user_ids that have to perform an action - :return: dict { record_id: [user_ids], } - """ - result = super(ir_needaction_mixin, self).get_needaction_user_ids(cr, uid, ids, context=context) - for obj in self.browse(cr, uid, ids, context=context): - if obj.message_unread == False and obj.user_id: - result[obj.id].append(obj.user_id.id) - return result diff --git a/addons/mail/doc/mail_subtype.rst b/addons/mail/doc/mail_subtype.rst deleted file mode 100644 index e63b67936cd..00000000000 --- a/addons/mail/doc/mail_subtype.rst +++ /dev/null @@ -1,70 +0,0 @@ -.. _mail_message_subtype: - -OpenChatter Pi (3.1415): Message Subtype -======================================== - - To overcome the problems of crowdy walls in system notification, We have added features of **Message Subtype** in mail. - -mail.message.subtype -++++++++++++++++++++ -``mail.message.subtype`` has following fields: - - - ``Name``: fields.char(' Message Subtype ', size = 128,required = True,help = 'Subtype Of Message'), - - ``model_ids``: fields.many2many('ir.model','mail_message_subtyp_message_rel','message_subtype_id', 'model_id', 'Model',help = "link some subtypes to several models, for projet/task"), - - ``default``: fields.boolean('Default', help = "When subscribing to the document, users will receive by default messages related to this subtype unless they uncheck this subtype"), - -mail.followers -++++++++++++++ - -In ``mail.followers`` we have added additional many2many field subtype ids : - - - ``subtype_ids``: fields.many2many('mail.message.subtype','mail_message_subtyp_rel','subscription_id', 'subtype_id', 'Subtype',help = "linking some subscription to several subtype for projet/task") - -mail.message -++++++++++++ - -In mail_message we have added additional field subtype_id which Indicates the Type of Message - - - ``subtype_id``: fields.many2one('mail.message.subtype', 'Subtype') - -mail.thread -+++++++++++ - - - In **message_post** method add the *subtype_id* field as parameter and set as default subtype 'Other'. - - def message_post(self, cr, uid, thread_id, body='', subject=False, msg_type='notification', parent_id=False, attachments=None, subtype='other', context=None, ``**kwargs``): - - - In **message_subscribe** method add the *subtype_ids* field as parameter.In this method if subtype_ids is None, it fatch the default true subtypes in mail.message.subtypes otherwise pass selected subtypes. - For update subtypes call **message_subscribe_udpate_subtypes** method - - def message_subscribe(self, cr, uid, ids, partner_ids,subtype_ids = None, context=None): - - - Add **message_subscribe_udpate_subtypes** method to update the subtype_ids in followers. - - def message_subscribe_udpate_subtypes(self, cr, uid, ids, user_id, subtype_ids,context=None): - followers_obj = self.pool.get('mail.followers') - followers_ids = followers_obj.search(cr, uid, [('res_model', '=', self._name), ('res_id', 'in', ids)]) - return followers_obj.write(cr, uid, followers_ids, {'subtype_ids': [(6, 0 , subtype_ids)]}, context = context) - -For Each Addons: -++++++++++++++++ - - - Add data of subtypes for each addons module. - - Add subtype field as parameter in **message_post** Method for each addons module. - -How It Works: -+++++++++++++ - - - In addons module when we Follow a Perticular document It display under the followers button. - - In sybtypes there are 3 default subtypes for each addons - 1) Email - 2) Comment - 3) Other - - In document display a default subtypes(which are true) related a perticular model_ids wise. - - Example:- - If I have open crm.lead, It display only subtypes of crm.lead - - - When we select subtype it update subtype_ids(which are checked) in mail.follower where match res_model & res_id of the current documents. - - when message created update subtype_id of that message in mail.message. - - In Feeds display only those notifications of documents which subtypes are selected diff --git a/addons/mail/doc/mail_thread.rst b/addons/mail/doc/mail_thread.rst deleted file mode 100644 index 9a59b7c967c..00000000000 --- a/addons/mail/doc/mail_thread.rst +++ /dev/null @@ -1,53 +0,0 @@ -.. _mail_thread: - -=========================== -mail.thread and OpenChatter -=========================== - -API -=== - -Writing messages and notifications ----------------------------------- - -``message_append`` - -Creates a new mail.message through message_create. The new message is attached -to the current mail.thread, containing all the details passed as parameters. -All attachments will be attached to the thread record as well as to the -actual message. - -This method calls message_create that will handle management of subscription -and notifications, and effectively create the message. - -If ``email_from`` is not set or ``type`` not set as 'email', a note message -is created (comment or system notification), without the usual envelope -attributes (sender, recipients, etc.). - -mail.group -++++++++++ - -A mail_group is a collection of users sharing messages in a discussion group. Group users are users that follow the mail group, using the subscription/follow mechanism of OpenSocial. A mail group has nothing in common wih res.users.group. -Additional information on fields: - - - ``member_ids``: user member of the groups are calculated with ``message_get_subscribers`` method from mail.thread - - ``member_count``: calculated with member_ids - - ``is_subscriber``: calculated with member_ids - -res.users -+++++++++ - -OpenChatter updates the res.users class: - - it adds a preference about sending emails when receiving a notification - - make a new user follow itself automatically - - create a welcome message when creating a new user, to make his arrival in OpenERP more friendly - - -Misc magic context keys -+++++++++++++++++++++++ - - - mail_create_nosubscribe: when creating a new record that inherit from mail_thread, - do not subscribe the creator to the document followers - - mail_create_nolog: do not log creation message - - mail_notify_noemail: do not send email notifications; partners to notify are - notified, i.e. a mail_notification is created, but no email is actually send diff --git a/addons/mail/doc/tracked_field.rst b/addons/mail/doc/tracked_field.rst deleted file mode 100644 index 8d06ae1fa41..00000000000 --- a/addons/mail/doc/tracked_field.rst +++ /dev/null @@ -1,18 +0,0 @@ -Tracked fields ---------------- -Tracked fields is generic logging system for the changes applied to some fields in open chatter. - - In the definition of a fields. you have to simply add tracked=True. - - When changing one of the fields having tracked=True on saved automatically write a log in openchatter for the changes in fields. - -How it works: -+++++++++++++ - - You have to add tracked=True in field defination as following. - - ``'stage_id': fields.many2one('project.task.type', 'Stage',tracked=True),`` - - For developed this feature we override write method of mail_thread. - - And make one mako template which shows old field and updated field. - -Open chatter log: -+++++++++++++++++ - - After changing field follower can show log of tracked field in open chatter as followed. - - Updated Fields: - - Stage: Analysis -> Specification \ No newline at end of file diff --git a/addons/mail/models/mail_template.py b/addons/mail/models/mail_template.py index d87cad5fec7..9f870b86ac2 100644 --- a/addons/mail/models/mail_template.py +++ b/addons/mail/models/mail_template.py @@ -45,7 +45,7 @@ def format_tz(env, dt, tz=False, format=False): format_time = langs.time_format or '%I-%M %p' fdate = ts.strftime(format_date).decode('utf-8') - ftime = ts.strftime(format_time) + ftime = ts.strftime(format_time).decode('utf-8') return "%s %s%s" % (fdate, ftime, (' (%s)' % tz) if tz else '') try: diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py index 71c196cd693..599cec0493c 100644 --- a/addons/mail/models/mail_thread.py +++ b/addons/mail/models/mail_thread.py @@ -5,6 +5,8 @@ import base64 import datetime import dateutil import email +import hashlib +import hmac import json import lxml import logging @@ -568,6 +570,13 @@ class MailThread(models.AbstractModel): because portal users have a different Inbox action than classic users. """ return 'mail.mail_channel_action_client_chat' + @api.model + def _generate_notification_token(self, base_link, params): + secret = self.env['ir.config_parameter'].sudo().get_param('database.secret') + token = '%s?%s' % (base_link, ' '.join('%s=%s' % (key, params[key]) for key in sorted(params.keys()))) + hm = hmac.new(str(secret), token, hashlib.sha1).hexdigest() + return hm + @api.multi def _notification_link_helper(self, link_type, **kwargs): if kwargs.get('message_id'): @@ -576,24 +585,29 @@ class MailThread(models.AbstractModel): } else: base_params = { - 'model': kwargs.pop('model', self._name), - 'res_id': kwargs.pop('res_id', self.ids and self.ids[0] or False), + 'model': kwargs.get('model', self._name), + 'res_id': kwargs.get('res_id', self.ids and self.ids[0] or False), } - link = False if link_type in ['view', 'assign', 'follow', 'unfollow']: params = dict(base_params) - link = '/mail/%s?%s' % (link_type, url_encode(params)) - elif link_type == 'workflow': - params = dict(base_params, signal=kwargs['signal']) - link = '/mail/workflow?%s' % url_encode(params) - elif link_type == 'method': - method = kwargs.pop('method') - params = dict(base_params, method=method, params=json.dumps(kwargs)) - link = '/mail/method?%s' % url_encode(params) + base_link = '/mail/%s' % link_type elif link_type == 'new': - params = dict(base_params, action_id=kwargs.get('action_id')) - link = '/mail/new?%s' % url_encode(params) + params = dict(base_params, action_id=kwargs.get('action_id', '')) + base_link = '/mail/new' + elif link_type == 'controller': + controller = kwargs.pop('controller') + params = dict(base_params) + params.pop('model') + base_link = '%s' % controller + else: + return '' + + if link_type not in ['view', 'new']: + token = self._generate_notification_token(base_link, params) + params['token'] = token + + link = '%s?%s' % (base_link, url_encode(params)) return link @api.multi @@ -1030,7 +1044,7 @@ class MailThread(models.AbstractModel): tools.decode_message_header(message, 'Cc'), tools.decode_message_header(message, 'Resent-To'), tools.decode_message_header(message, 'Resent-Cc')]) - rcpt_tos_localparts = [e.split('@')[0] for e in tools.email_split(rcpt_tos)] + rcpt_tos_localparts = [e.split('@')[0].lower() for e in tools.email_split(rcpt_tos)] # 0. Verify whether this is a bounced email and use it to collect bounce data and update notifications for customers if bounce_alias and bounce_alias in email_to_localpart: diff --git a/addons/mail/models/update.py b/addons/mail/models/update.py index a5717353a79..7e2abfcbfd8 100644 --- a/addons/mail/models/update.py +++ b/addons/mail/models/update.py @@ -31,13 +31,13 @@ class PublisherWarrantyContract(AbstractModel): limit_date = datetime.datetime.now() limit_date = limit_date - datetime.timedelta(15) limit_date_str = limit_date.strftime(misc.DEFAULT_SERVER_DATETIME_FORMAT) - nbr_users = Users.search_count([]) - nbr_active_users = Users.search_count([("login_date", ">=", limit_date_str)]) + nbr_users = Users.search_count([('active', '=', True)]) + nbr_active_users = Users.search_count([("login_date", ">=", limit_date_str), ('active', '=', True)]) nbr_share_users = 0 nbr_active_share_users = 0 if "share" in Users._fields: - nbr_share_users = Users.search_count([("share", "=", True)]) - nbr_active_share_users = Users.search_count([("share", "=", True), ("login_date", ">=", limit_date_str)]) + nbr_share_users = Users.search_count([("share", "=", True), ('active', '=', True)]) + nbr_active_share_users = Users.search_count([("share", "=", True), ("login_date", ">=", limit_date_str), ('active', '=', True)]) user = self.env.user domain = [('application', '=', True), ('state', 'in', ['installed', 'to upgrade', 'to remove'])] apps = self.env['ir.module.module'].sudo().search_read(domain, ['name']) diff --git a/addons/mail/static/description/index.html b/addons/mail/static/description/index.html index 0ac3584090f..54d6016fd9f 100644 --- a/addons/mail/static/description/index.html +++ b/addons/mail/static/description/index.html @@ -1,6 +1,6 @@
-

Enhance team communication

+

Enhance team communication

Group and private chat, integrated with all modules

@@ -10,7 +10,7 @@
-

Engage conversation with colleagues

+

Engage conversation with colleagues

A powerful communication tool

@@ -27,7 +27,7 @@
-

Chat in live from anywhere

+

Chat in live from anywhere

Keep conversations open in other modules

@@ -43,7 +43,7 @@

-

Communicate with the right target

+

Communicate with the right target

Create channels and private groups

Start discussion with the right people with channels and allow any employee to join in to the conversation.Create private channels to invite only a selected group of people and prevent unrelated employees to pollute the conversations. @@ -56,7 +56,7 @@

-

Personalize notifications

+

Personalize notifications

Send notifications only when you want to

@@ -73,7 +73,7 @@

-

Integrated with all modules

+

Integrated with all modules

Keep track of all actions within one window

Follow any discussion from any module to automatically see updates and discussions on those tasks in your news feed. Create public and private groups to invite participants, personalize the notifications system and add those groups to specific tasks to keep track of the evolution of all projects. diff --git a/addons/mail/static/src/xml/thread.xml b/addons/mail/static/src/xml/thread.xml index b5ed0081d74..b9579be0790 100644 --- a/addons/mail/static/src/xml/thread.xml +++ b/addons/mail/static/src/xml/thread.xml @@ -94,7 +94,7 @@ (from #) - + diff --git a/addons/mail/tests/__init__.py b/addons/mail/tests/__init__.py index 413ea870c3c..1ff431bd633 100644 --- a/addons/mail/tests/__init__.py +++ b/addons/mail/tests/__init__.py @@ -10,3 +10,4 @@ import test_message_track import test_mail_template import test_invite import test_ir_actions +import test_update_notification diff --git a/addons/mail/tests/test_update_notification.py b/addons/mail/tests/test_update_notification.py new file mode 100644 index 00000000000..7621e4056a1 --- /dev/null +++ b/addons/mail/tests/test_update_notification.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +from odoo.tests.common import TransactionCase + + +class TestUpdateNotification(TransactionCase): + def test_user_count(self): + ping_msg = self.env['publisher_warranty.contract'].with_context(active_test=False)._get_message() + user_count = self.env['res.users'].search_count([('active', '=', True)]) + self.assertEqual(ping_msg.get('nbr_users'), user_count, 'Update Notification: Users count is badly computed in ping message') + share_user_count = self.env['res.users'].search_count([('active', '=', True), ('share', '=', True)]) + self.assertEqual(ping_msg.get('nbr_share_users'), share_user_count, 'Update Notification: Portal Users count is badly computed in ping message') diff --git a/addons/marketing_campaign/models/marketing_campaign.py b/addons/marketing_campaign/models/marketing_campaign.py index 55df93b4846..2aa745dfaa1 100644 --- a/addons/marketing_campaign/models/marketing_campaign.py +++ b/addons/marketing_campaign/models/marketing_campaign.py @@ -144,7 +144,7 @@ class MarketingCampaign(models.Model): similar_res_ids = self.env[self.object_id.model].search([(unique_field.name, '=', unique_value)]) if similar_res_ids: duplicate_workitem_domain = [ - ('res_id', 'in', similar_res_ids), + ('res_id', 'in', similar_res_ids.ids), ('campaign_id', '=', self.id) ] return self.env['marketing.campaign.workitem'].search(duplicate_workitem_domain) diff --git a/addons/marketing_campaign/static/description/index.html b/addons/marketing_campaign/static/description/index.html index 5711c02e503..cfc189727e7 100644 --- a/addons/marketing_campaign/static/description/index.html +++ b/addons/marketing_campaign/static/description/index.html @@ -1,6 +1,6 @@

-

Better manage leads with marketing automation

+

Better manage leads with marketing automation

Nurture all of your prospects with a personal and targeted message

@@ -10,7 +10,7 @@
-

Generate new opportunities effortlessly

+

Generate new opportunities effortlessly

Streamline your marketing & take care of your leads 24/7

@@ -45,7 +45,7 @@
-

Deliver a message that resonates with your prospects

+

Deliver a message that resonates with your prospects

Create advanced and personal emails that don't look automated

@@ -69,7 +69,7 @@
-

Shorten your sales cycle

+

Shorten your sales cycle

Manage your prospects better by combining lead scoring and lead automation

@@ -84,7 +84,7 @@
-

Follow the ROI of all your marketing activities

+

Follow the ROI of all your marketing activities

Make smarter decisions thanks to our powerful analytics reports

diff --git a/addons/marketing_campaign/views/marketing_campaign_views.xml b/addons/marketing_campaign/views/marketing_campaign_views.xml index af248d12987..35c220b515d 100644 --- a/addons/marketing_campaign/views/marketing_campaign_views.xml +++ b/addons/marketing_campaign/views/marketing_campaign_views.xml @@ -109,7 +109,7 @@ marketing.campaign - + diff --git a/addons/mass_mailing/models/mass_mailing.py b/addons/mass_mailing/models/mass_mailing.py index b6bef12b6c4..dcec29bd48d 100644 --- a/addons/mass_mailing/models/mass_mailing.py +++ b/addons/mass_mailing/models/mass_mailing.py @@ -424,6 +424,12 @@ class MassMailing(models.Model): # Technical stuff #------------------------------------------------------ + @api.model + def name_create(self, name): + """ _rec_name is source_id, creates a utm.source instead """ + mass_mailing = self.create({'name': name}) + return mass_mailing.name_get()[0] + @api.multi def copy(self, default=None): self.ensure_one() diff --git a/addons/mass_mailing/static/description/index.html b/addons/mass_mailing/static/description/index.html index addbd9738e1..f47619a35cb 100644 --- a/addons/mass_mailing/static/description/index.html +++ b/addons/mass_mailing/static/description/index.html @@ -1,6 +1,6 @@

-

Beautiful email marketing solution

+

Beautiful email marketing solution

Grow your subscribers base with powerful email marketing

@@ -12,7 +12,7 @@
-

Build awesome campaigns in minutes

+

Build awesome campaigns in minutes

Start from scratch or pick one of our many themes!

@@ -44,7 +44,7 @@

-

Send the right message to the right audience

+

Send the right message to the right audience

Segment your database for better open and click rates

@@ -59,7 +59,7 @@
-

Reach your target on any device

+

Reach your target on any device

Fully responsive, 100% mobile friendly

@@ -74,7 +74,7 @@

-

Track & improve your campaigns

+

Track & improve your campaigns

Rely on stats and data to send better emails

@@ -88,7 +88,7 @@
-

Go one step further with advanced features

+

Go one step further with advanced features

Take full control of every aspect of your mailing campaigns

diff --git a/addons/mass_mailing/static/src/css/mass_mailing_popup.css b/addons/mass_mailing/static/src/css/mass_mailing_popup.css index 688c469228b..2408f53f218 100644 --- a/addons/mass_mailing/static/src/css/mass_mailing_popup.css +++ b/addons/mass_mailing/static/src/css/mass_mailing_popup.css @@ -226,7 +226,7 @@ fieldset[disabled] #o_newsletter_popup .btn.btn-success.active { } #o_newsletter_popup .o_popup_modal_content .o_popup_modal_body .o_popup_content_dev .o_popup_message font { font-weight: 600; - color: #a24689; + color: #875A7B; font-size: 2.5em; font-style: italic; position: absolute; @@ -234,7 +234,7 @@ fieldset[disabled] #o_newsletter_popup .btn.btn-success.active { margin-left: -0.65em; } #o_newsletter_popup .o_popup_modal_content .o_popup_modal_body .o_popup_content_dev .o_popup_message strong { - color: #a24689; + color: #875A7B; } #o_newsletter_popup .o_popup_modal_content .o_popup_modal_body .o_popup_content_dev .o_message_paragraph { position: relative; diff --git a/addons/mass_mailing/views/mass_mailing_views.xml b/addons/mass_mailing/views/mass_mailing_views.xml index e3670ba18ea..ddd5e153696 100644 --- a/addons/mass_mailing/views/mass_mailing_views.xml +++ b/addons/mass_mailing/views/mass_mailing_views.xml @@ -10,7 +10,7 @@ parent="mass_mailing_menu_root" sequence="100"/> - + List Subscription @@ -25,7 +25,7 @@ [('opt_out','=',True)] {'group_by': ['unsubscription_date:month'], 'col_group_by': ['list_id']} - + mail.mass_mailing.contact.search mail.mass_mailing.contact @@ -64,7 +64,7 @@ - + mail.mass_mailing.contact.graph mail.mass_mailing.contact @@ -245,7 +245,7 @@

- This mass mailing is scheduled to + This mass mailing is scheduled for

@@ -326,7 +326,7 @@
-
@@ -467,7 +467,7 @@ form,kanban,tree { 'search_default_mass_mailing_campaign_id': [active_id], - 'default_mass_mailing_campaign_id': active_id, + 'default_mass_mailing_campaign_id': active_id, } @@ -743,7 +743,7 @@ - + Process Mass Mailing Queue diff --git a/addons/mass_mailing/views/snippets_themes.xml b/addons/mass_mailing/views/snippets_themes.xml index f6d2f3991ee..dad98262117 100644 --- a/addons/mass_mailing/views/snippets_themes.xml +++ b/addons/mass_mailing/views/snippets_themes.xml @@ -84,30 +84,6 @@ &nbsp; - -
- - - - - - - - - - - - - - - - - - - - -
-
@@ -128,30 +104,6 @@

&nbsp; - -
- - - - - - - - - - - - - - - - - - - - -
-
@@ -765,35 +717,9 @@
- - - @@ -818,36 +744,12 @@

-
diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py index d0f5a2f00f4..e21b492af63 100644 --- a/addons/mrp/models/mrp_production.py +++ b/addons/mrp/models/mrp_production.py @@ -230,7 +230,10 @@ class MrpProduction(models.Model): self.bom_id = False else: bom = self.env['mrp.bom']._bom_find(product=self.product_id, picking_type=self.picking_type_id, company_id=self.company_id.id) - self.bom_id = bom.id + if bom.type == 'normal': + self.bom_id = bom.id + else: + self.bom_id = False self.product_uom_id = self.product_id.uom_id.id return {'domain': {'product_uom_id': [('category_id', '=', self.product_id.uom_id.category_id.id)]}} diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py index 5c539195444..853bd6d56a4 100644 --- a/addons/mrp/models/stock_move.py +++ b/addons/mrp/models/stock_move.py @@ -2,6 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import api, exceptions, fields, models, _ +from odoo.exceptions import UserError from odoo.tools import float_compare from odoo.addons import decimal_precision as dp @@ -241,6 +242,8 @@ class StockMove(models.Model): else: for movelot in move.move_lot_ids: if float_compare(movelot.quantity_done, 0, precision_rounding=rounding) > 0: + if not movelot.lot_id: + raise UserError(_('You need to supply a lot/serial number.')) qty = move.product_uom._compute_quantity(movelot.quantity_done, move.product_id.uom_id) quants = quant_obj.quants_get_preferred_domain(qty, move, lot_id=movelot.lot_id.id, domain=main_domain, preferred_domain_list=preferred_domain_list) self.env['stock.quant'].quants_move(quants, move, move.location_dest_id, lot_id = movelot.lot_id.id) diff --git a/addons/mrp/report/mrp_bom_cost_report.py b/addons/mrp/report/mrp_bom_cost_report.py index 93ae3ad64e1..cbbdf935497 100644 --- a/addons/mrp/report/mrp_bom_cost_report.py +++ b/addons/mrp/report/mrp_bom_cost_report.py @@ -26,7 +26,7 @@ class MrpBomCost(models.AbstractModel): 'attributes': attributes} total = 0.0 for bom_line, line_data in result2: - price_uom = self.env['product.uom']._compute_qty(bom_line.product_id.uom_id.id, bom_line.product_id.standard_price, bom_line.product_uom_id.id) + price_uom = bom_line.product_uom_id._compute_quantity(bom_line.product_id.standard_price, bom_line.product_uom_id) line = { 'product_id': bom_line.product_id, 'product_uom_qty': line_data['qty'], #line_data needed for phantom bom explosion diff --git a/addons/mrp/static/description/index.html b/addons/mrp/static/description/index.html index 5fe98beeb07..878d0b7942c 100644 --- a/addons/mrp/static/description/index.html +++ b/addons/mrp/static/description/index.html @@ -1,6 +1,6 @@
-

Simplify manufacturing processes

+

Simplify manufacturing processes

Online and easy MRP application

@@ -10,7 +10,7 @@
-

Efficiently schedule your manufacturing

+

Efficiently schedule your manufacturing

Keep track of resource capacities and avoid bottlenecks

@@ -35,7 +35,7 @@
-

Enjoy complete flexibility

+

Enjoy complete flexibility

A simple and powerful user interface

@@ -50,7 +50,7 @@

-

Work with Lists, Calendars or Gantt charts

+

Work with Lists, Calendars or Gantt charts

Organize manufacturing orders and work orders in any way you like.

Process upcoming orders from the List view, control in the Calendar view and edit the proposed schedule in the Gantt view. @@ -85,7 +85,7 @@

-

Define Flexible Master Data

+

Define Flexible Master Data

Products, bill of materials and routings

Have the flexibility to create multi-level bills of materials, optional routing, version changes and phantom bills of materials. You can use BoM for kits or for manufacturing orders. @@ -93,7 +93,7 @@

-

Advanced analytic features

+

Advanced analytic features

Get accurate data on your process

Track the changes in stock value according to the level of manufacturing activities as it progresses through the transformation process. @@ -105,7 +105,7 @@

-

Fully integrated with Operations

+

Fully integrated with Operations

Sales, purchases, inventory and accounting integration

diff --git a/addons/mrp/views/mrp_bom_views.xml b/addons/mrp/views/mrp_bom_views.xml index 45e7d7cc842..aeccdbfc1ad 100644 --- a/addons/mrp/views/mrp_bom_views.xml +++ b/addons/mrp/views/mrp_bom_views.xml @@ -181,6 +181,7 @@ + diff --git a/addons/mrp/views/mrp_production_views.xml b/addons/mrp/views/mrp_production_views.xml index 003e703818a..993b8b8fb32 100644 --- a/addons/mrp/views/mrp_production_views.xml +++ b/addons/mrp/views/mrp_production_views.xml @@ -93,7 +93,7 @@ string="Update" states="confirmed,planned,progress" class="oe_link" attrs="{'invisible': [('state','=','done')]}"/>
-

Get your work done

+

Get your work done

Organize your tasks with notes and to-do lists

@@ -12,7 +12,7 @@
-

Personal to-do lists that boost productivity

+

Personal to-do lists that boost productivity

Achieve your goals and objectives easier than ever

@@ -27,7 +27,7 @@

-

Better manage your tasks and projects

+

Better manage your tasks and projects

Gather ideas on the fly and collaborate with your colleagues

@@ -74,7 +74,7 @@
-

Customize your notes and to-do lists

+

Customize your notes and to-do lists

Odoo Notes adapts to your needs and habits

@@ -89,7 +89,7 @@

-

Integrate Odoo Notes into your workflow

+

Integrate Odoo Notes into your workflow

diff --git a/addons/point_of_sale/security/point_of_sale_security.xml b/addons/point_of_sale/security/point_of_sale_security.xml index 649d9df8e57..f29a37cfcfd 100644 --- a/addons/point_of_sale/security/point_of_sale_security.xml +++ b/addons/point_of_sale/security/point_of_sale_security.xml @@ -36,12 +36,6 @@ [(1, '=', 1)] - - Point Of Sale Cashbox Line POS User - - - [('bank_statement_id.pos_session_id', '!=', False)] - Point Of Sale Cashbox Line Accountant diff --git a/addons/point_of_sale/static/description/index.html b/addons/point_of_sale/static/description/index.html index df254603ebf..3635eda13b8 100644 --- a/addons/point_of_sale/static/description/index.html +++ b/addons/point_of_sale/static/description/index.html @@ -1,6 +1,6 @@
-

Smart Point of Sale

+

Smart Point of Sale

Easy to use & compatible with any device.

Watch the video @@ -10,7 +10,7 @@
-

User-friendly Point of Sale

+

User-friendly Point of Sale

Set-up in minutes, sell in seconds

@@ -31,7 +31,7 @@
-

An interface designed for productivity

+

An interface designed for productivity

The point of sale that keeps everything where you need it

@@ -65,7 +65,7 @@
-

Online or offline interface, compatible with any hardware

+

Online or offline interface, compatible with any hardware

Odoo's Point of Sale stays reliable even if your connection is not

@@ -88,7 +88,7 @@
-

Keep in touch with your customers

+

Keep in touch with your customers

And keep them coming back with loyalty programs.

@@ -108,7 +108,7 @@

-

Integrated inventory management

+

Integrated inventory management

Odoo Point of Sale will facilitate your inventory management

@@ -138,7 +138,7 @@

-

Deliver in-store customer service

+

Deliver in-store customer service

Repairs, warranties, deliveries, etc.

@@ -153,7 +153,7 @@
-

Unified data across all shops to improve your sales

+

Unified data across all shops to improve your sales

Sync products, prices and customers with no effort

@@ -171,7 +171,7 @@

-

Know your customers

+

Know your customers

In the store and online

diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 1282d35d3a9..b34e9c32290 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -1924,7 +1924,7 @@ exports.Order = Backbone.Model.extend({ }, initialize_validation_date: function () { - this.validation_date = this.validation_date || new Date(); + this.validation_date = new Date(); }, set_tip: function(tip) { diff --git a/addons/product/models/product.py b/addons/product/models/product.py index 0416c68598b..10ec0e20208 100644 --- a/addons/product/models/product.py +++ b/addons/product/models/product.py @@ -387,7 +387,7 @@ class ProductProduct(models.Model): for product in self.sudo(): # display only the attributes with multiple possible values on the template variable_attributes = product.attribute_line_ids.filtered(lambda l: len(l.value_ids) > 1).mapped('attribute_id') - variant = ", ".join([v.name for v in product.attribute_value_ids if v.attribute_id in variable_attributes]) + variant = product.attribute_value_ids._variant_name(variable_attributes) name = variant and "%s (%s)" % (product.name, variant) or product.name sellers = [] diff --git a/addons/product/models/product_attribute.py b/addons/product/models/product_attribute.py index a0cdb0e9805..d90e070c05e 100644 --- a/addons/product/models/product_attribute.py +++ b/addons/product/models/product_attribute.py @@ -74,6 +74,10 @@ class ProductAttributevalue(models.Model): raise UserError(_('The operation cannot be completed:\nYou are trying to delete an attribute value with a reference on a product variant.')) return super(ProductAttributevalue, self).unlink() + @api.multi + def _variant_name(self, variable_attributes): + return ", ".join([v.name for v in self if v.attribute_id in variable_attributes]) + class ProductAttributePrice(models.Model): _name = "product.attribute.price" diff --git a/addons/product/models/product_template.py b/addons/product/models/product_template.py index db3d2d81ce0..2b2508cce9d 100644 --- a/addons/product/models/product_template.py +++ b/addons/product/models/product_template.py @@ -185,47 +185,47 @@ class ProductTemplate(models.Model): else: self.write({'list_price': self.price}) - @api.depends('product_variant_count', 'product_variant_ids.standard_price') + @api.depends('product_variant_ids', 'product_variant_ids.standard_price') def _compute_standard_price(self): - unique_variants = self.filtered(lambda template: template.product_variant_count == 1) + unique_variants = self.filtered(lambda template: len(template.product_variant_ids) == 1) for template in unique_variants: - template.standard_price = template.product_variant_id.standard_price + template.standard_price = template.product_variant_ids.standard_price for template in (self - unique_variants): template.standard_price = 0.0 @api.one def _set_standard_price(self): - if self.product_variant_count == 1: + if len(self.product_variant_ids) == 1: self.product_variant_ids.standard_price = self.standard_price def _search_standard_price(self, operator, value): products = self.env['product.product'].search([('standard_price', operator, value)], limit=None) return [('id', 'in', products.mapped('product_tmpl_id').ids)] - @api.depends('product_variant_count', 'product_variant_ids.volume') + @api.depends('product_variant_ids', 'product_variant_ids.volume') def _compute_volume(self): - unique_variants = self.filtered(lambda template: template.product_variant_count == 1) + unique_variants = self.filtered(lambda template: len(template.product_variant_ids) == 1) for template in unique_variants: - template.volume = template.product_variant_id.volume + template.volume = template.product_variant_ids.volume for template in (self - unique_variants): template.volume = 0.0 @api.one def _set_volume(self): - if self.product_variant_count == 1: + if len(self.product_variant_ids) == 1: self.product_variant_ids.volume = self.volume - @api.depends('product_variant_count', 'product_variant_ids.weight') + @api.depends('product_variant_ids', 'product_variant_ids.weight') def _compute_weight(self): - unique_variants = self.filtered(lambda template: template.product_variant_count == 1) + unique_variants = self.filtered(lambda template: len(template.product_variant_ids) == 1) for template in unique_variants: - template.weight = template.product_variant_id.weight + template.weight = template.product_variant_ids.weight for template in (self - unique_variants): template.weight = 0.0 @api.one def _set_weight(self): - if self.product_variant_count == 1: + if len(self.product_variant_ids) == 1: self.product_variant_ids.weight = self.weight @api.one @@ -233,17 +233,17 @@ class ProductTemplate(models.Model): def _compute_product_variant_count(self): self.product_variant_count = len(self.product_variant_ids) - @api.depends('product_variant_count', 'product_variant_ids.default_code') + @api.depends('product_variant_ids', 'product_variant_ids.default_code') def _compute_default_code(self): - unique_variants = self.filtered(lambda template: template.product_variant_count == 1) + unique_variants = self.filtered(lambda template: len(template.product_variant_ids) == 1) for template in unique_variants: - template.default_code = template.product_variant_id.default_code + template.default_code = template.product_variant_ids.default_code for template in (self - unique_variants): template.default_code = '' @api.one def _set_default_code(self): - if self.product_variant_count == 1: + if len(self.product_variant_ids) == 1: self.product_variant_ids.default_code = self.default_code @api.constrains('uom_id', 'uom_po_id') @@ -368,14 +368,7 @@ class ProductTemplate(models.Model): @api.multi def create_variant_ids(self): Product = self.env["product.product"] - for tmpl_id in self.with_context(active_test=False): - # list of values combination - existing_variants = [set(variant.attribute_value_ids.ids) for variant in tmpl_id.product_variant_ids] - variant_matrix = itertools.product(*(line.value_ids for line in tmpl_id.attribute_line_ids if line.value_ids and line.value_ids[0].attribute_id.create_variant)) - variant_matrix = map(lambda record_list: reduce(lambda x, y: x+y, record_list, self.env['product.attribute.value']), variant_matrix) - to_create_variants = filter(lambda rec_set: set(rec_set.ids) not in existing_variants, variant_matrix) - # adding an attribute with only one value should not recreate product # write this attribute on every product to make sure we don't lose them variant_alone = tmpl_id.attribute_line_ids.filtered(lambda line: len(line.value_ids) == 1).mapped('value_ids') @@ -383,6 +376,12 @@ class ProductTemplate(models.Model): updated_products = tmpl_id.product_variant_ids.filtered(lambda product: value_id.attribute_id not in product.mapped('attribute_value_ids.attribute_id')) updated_products.write({'attribute_value_ids': [(4, value_id.id)]}) + # list of values combination + existing_variants = [set(variant.attribute_value_ids.ids) for variant in tmpl_id.product_variant_ids] + variant_matrix = itertools.product(*(line.value_ids for line in tmpl_id.attribute_line_ids if line.value_ids and line.value_ids[0].attribute_id.create_variant)) + variant_matrix = map(lambda record_list: reduce(lambda x, y: x+y, record_list, self.env['product.attribute.value']), variant_matrix) + to_create_variants = filter(lambda rec_set: set(rec_set.ids) not in existing_variants, variant_matrix) + # check product variants_to_activate = self.env['product.product'] variants_to_unlink = self.env['product.product'] diff --git a/addons/project/data/project_mail_template_data.xml b/addons/project/data/project_mail_template_data.xml index db637646b85..a1a326bd35f 100644 --- a/addons/project/data/project_mail_template_data.xml +++ b/addons/project/data/project_mail_template_data.xml @@ -21,7 +21,7 @@ - Auto Login + Auto Login @@ -58,7 +58,7 @@ project planner to activate extra features - (${user.env['web.planner'].get_planner_progress('planner_project')}% done) + (${user.env['web.planner'].get_planner_progress('planner_project')}% done)

  • diff --git a/addons/project/static/description/index.html b/addons/project/static/description/index.html index 561ea84dc7b..9c3d09703b8 100644 --- a/addons/project/static/description/index.html +++ b/addons/project/static/description/index.html @@ -1,6 +1,6 @@
    -

    Agile Project Management

    +

    Agile Project Management

    Beautiful. Easy. Open Source.

    @@ -12,7 +12,7 @@
    -

    Modern interface

    +

    Modern interface

    What you need, the way you like it

    @@ -47,7 +47,7 @@
    -

    Harness the power of visual information

    +

    Harness the power of visual information

    Flexible project management made easy.

    @@ -62,7 +62,7 @@
    -

    Discuss tasks, Share documents

    +

    Discuss tasks, Share documents

    Add quick notes or attach files to any task and stay connected to your team. Forget endless follow-up meetings to keep track of projects. Communicate through the chatter, write in a shared, real time pad or use live chat - all in a single interface! @@ -76,7 +76,7 @@

    -

    Conceptualize project scope within an easy-to-read overview

    +

    Conceptualize project scope within an easy-to-read overview

    A functional and dynamic graph view for every task

    diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py index 3abe4839024..2b5080ce1d1 100644 --- a/addons/purchase/models/purchase.py +++ b/addons/purchase/models/purchase.py @@ -389,7 +389,8 @@ class PurchaseOrder(models.Model): 'date': self.date_order, 'origin': self.name, 'location_dest_id': self._get_destination_location(), - 'location_id': self.partner_id.property_stock_supplier.id + 'location_id': self.partner_id.property_stock_supplier.id, + 'company_id': self.company_id.id, } @api.multi diff --git a/addons/purchase/static/description/index.html b/addons/purchase/static/description/index.html index b32ff18b9a5..f5ee43a3612 100644 --- a/addons/purchase/static/description/index.html +++ b/addons/purchase/static/description/index.html @@ -1,6 +1,6 @@
    -

    Easily manage suppliers and purchase orders

    +

    Easily manage suppliers and purchase orders

    Improve your supply chain & inventory performance

    @@ -12,7 +12,7 @@
    -

    Automate your purchasing workflow

    +

    Automate your purchasing workflow

    Automatically send RfQs to your suppliers based on your stocks levels

    @@ -27,7 +27,7 @@

    -

    Control Products and Invoices

    +

    Control Products and Invoices

    Manage refunds, product receivals and quality control

    diff --git a/addons/purchase_requisition/views/purchase_requisition_views.xml b/addons/purchase_requisition/views/purchase_requisition_views.xml index d5177adf924..61fac151782 100644 --- a/addons/purchase_requisition/views/purchase_requisition_views.xml +++ b/addons/purchase_requisition/views/purchase_requisition_views.xml @@ -139,7 +139,7 @@ - + diff --git a/addons/rating_project/data/project_data.xml b/addons/rating_project/data/project_data.xml index d8089c709e5..5f7d4a09b7a 100644 --- a/addons/rating_project/data/project_data.xml +++ b/addons/rating_project/data/project_data.xml @@ -99,7 +99,7 @@ % if object.project_id.rating_status == 'periodic':

    This customer survey is sent ${object.project_id.rating_status_period} as long as the task is in the ${object.stage_id.name} stage. % endif -

    Email automatically sent by Odoo Project for ${object.project_id.company_id.name}

    +

    Email automatically sent by Odoo Project for ${object.project_id.company_id.name}

    diff --git a/addons/sale/data/mail_template_data.xml b/addons/sale/data/mail_template_data.xml index de2d509539d..4debb1e86d6 100644 --- a/addons/sale/data/mail_template_data.xml +++ b/addons/sale/data/mail_template_data.xml @@ -11,7 +11,7 @@ - ${(object.name or '').replace('/','_')}_${object.state == 'draft' and 'draft' or ''} + ${(object.name or '').replace('/','_')}${object.state == 'draft' and '_draft' or ''} ${object.partner_id.lang} Dear ${object.partner_id.name} @@ -70,7 +70,7 @@ from ${object.company_id.name}. - +
    @@ -101,7 +101,7 @@ from ${object.company_id.name}.
    - +
    ${user.company_id.name}
    diff --git a/addons/sale/static/description/index.html b/addons/sale/static/description/index.html index 0c0360eec42..334ab206f51 100644 --- a/addons/sale/static/description/index.html +++ b/addons/sale/static/description/index.html @@ -1,6 +1,6 @@
    -

    Easy quotation software

    +

    Easy quotation software

    Send polished quotation with e-Signature and online payment.

    @@ -12,7 +12,7 @@
    -

    Your quotes online from proposal to eSignature

    +

    Your quotes online from proposal to eSignature

    @@ -78,7 +78,7 @@
    -

    Sell more with clean quotations

    +

    Sell more with clean quotations

    Better showcase your products and services.

    Send clear and complete quotations to your prospects. Add product descriptions, beautiful images and additional information simply by dragging and dropping building blocks. @@ -92,7 +92,7 @@

    -

    Great prices for great revenues

    +

    Great prices for great revenues

    @@ -116,7 +116,7 @@

    -

    Order & contract sorting

    +

    Order & contract sorting

    Automate operations and focus on relations and revenues

    diff --git a/addons/sale/static/src/js/tour.js b/addons/sale/static/src/js/tour.js index da11aa59e15..177f1d06a34 100644 --- a/addons/sale/static/src/js/tour.js +++ b/addons/sale/static/src/js/tour.js @@ -61,7 +61,7 @@ tour.register('sale_tour', { }, { trigger: ".o_sale_confirm", extra_trigger: ".o_sale_order.o_form_readonly", - content: _t("

    Confirm the order if the customer purchases.

    The customer can also confirm the order directly from the email he received.

    "), + content: _t("

    Confirm the order if the customer purchases.

    "), position: "bottom" }, { trigger: ".breadcrumb li:not(.active):last", diff --git a/addons/sale_crm/static/src/js/tour.js b/addons/sale_crm/static/src/js/tour.js index cd3fff9d04e..392c93c0804 100644 --- a/addons/sale_crm/static/src/js/tour.js +++ b/addons/sale_crm/static/src/js/tour.js @@ -14,7 +14,7 @@ var quotation_button_step_index = _.findIndex(tour.tours.sale_tour.steps, functi tour.tours.sale_tour.steps.splice(quotation_button_step_index, 1, { trigger: ".o_kanban_manage_button_section > a", content: _t("Click here to see more options."), - position: "bottom" + position: "right" }, { trigger: ".o_quotation_view_button", content: _t("Let's have a look at the quotations of this sales team."), diff --git a/addons/sale_margin/views/sale_margin_view.xml b/addons/sale_margin/views/sale_margin_view.xml index fa9c56fe28a..b0da87326fa 100644 --- a/addons/sale_margin/views/sale_margin_view.xml +++ b/addons/sale_margin/views/sale_margin_view.xml @@ -20,7 +20,7 @@ - + diff --git a/addons/stock/static/description/index.html b/addons/stock/static/description/index.html index 287088066f9..fad948575af 100644 --- a/addons/stock/static/description/index.html +++ b/addons/stock/static/description/index.html @@ -1,6 +1,6 @@
    -

    Maximize your warehouse efficiency

    +

    Maximize your warehouse efficiency

    Modern online warehouse management software.

    @@ -12,7 +12,7 @@
    -

    Improve performance and process time

    +

    Improve performance and process time

    Better organize your warehouse with the smart double entry inventory system

    Get the most efficient stocking method and improve all your internal operations. Odoo's double-entry inventory has no stock input, output or transformation. Instead, all operations are stock moves between locations. @@ -25,7 +25,7 @@

    -

    Reduce your stock level and avoid stockouts

    +

    Reduce your stock level and avoid stockouts

    Take advantage of automatic replenishment methods

    @@ -42,7 +42,7 @@
    -

    Enjoy advanced and automated functions

    +

    Enjoy advanced and automated functions

    The most advanced systems in a very simple user interface

    @@ -95,7 +95,7 @@

    -

    Experience full traceability

    +

    Experience full traceability

    Keep track of every stock move

    @@ -108,7 +108,7 @@
    -

    Get clear & complete reporting and dashboard

    +

    Get clear & complete reporting and dashboard

    Keep an eye on the efficiency of your warehouse

    diff --git a/addons/stock_dropshipping/data/stock_data.xml b/addons/stock_dropshipping/data/stock_data.xml index 92794bddeca..59c95eb8899 100644 --- a/addons/stock_dropshipping/data/stock_data.xml +++ b/addons/stock_dropshipping/data/stock_data.xml @@ -1,6 +1,6 @@ - + Dropship DS diff --git a/addons/survey/static/description/index.html b/addons/survey/static/description/index.html index c83b5060f0b..0f16f3ec7b8 100644 --- a/addons/survey/static/description/index.html +++ b/addons/survey/static/description/index.html @@ -1,6 +1,6 @@

    -

    Get more insights.

    +

    Get more insights.

    Improve the performance of your organization with Odoo Surveys.

    @@ -12,7 +12,7 @@
    -

    Easily create appealing online surveys

    +

    Easily create appealing online surveys

    Design, share and analyze in three intuitive steps

    One platform to manage it all: automate appraisals, satisfaction surveys, marketing campaign, feedback forms, etc. @@ -56,7 +56,7 @@

    -

    Simple and intuitive from design to analysis

    +

    Simple and intuitive from design to analysis

    Gathering information is no longer time consuming

    diff --git a/addons/survey/wizard/survey_email_compose_message.xml b/addons/survey/wizard/survey_email_compose_message.xml index 08ab4095768..b92eef6809c 100644 --- a/addons/survey/wizard/survey_email_compose_message.xml +++ b/addons/survey/wizard/survey_email_compose_message.xml @@ -10,7 +10,7 @@ Hello,

    We are conducting a survey, and your response would be appreciated.

    -

    Please, click here to start survey

    +

    Please, click here to start survey

    Thanks for your participation!

    ]]> diff --git a/addons/utm/__manifest__.py b/addons/utm/__manifest__.py index 4d704a85a37..66fb0775f94 100644 --- a/addons/utm/__manifest__.py +++ b/addons/utm/__manifest__.py @@ -13,5 +13,5 @@ Enable UTM trackers in shared links. 'data/utm_data.xml' ], 'demo': [], - 'auto_install': True, + 'auto_install': False, } diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index ce609a3b3e5..d8c9c0d237e 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -695,6 +695,15 @@ class Database(http.Controller): error = "Master password update error: %s" % e return self._render_template(error=error) + @http.route('/web/database/list', type='json', auth='none') + def list(self): + """ + Used by Mobile application for listing database + :return: List of databases + :rtype: list + """ + return http.db_list() + class Session(http.Controller): @http.route('/web/session/get_session_info', type='json', auth="none") diff --git a/addons/web/models/ir_http.py b/addons/web/models/ir_http.py index 8d64ab0ad1d..1ab8943f835 100644 --- a/addons/web/models/ir_http.py +++ b/addons/web/models/ir_http.py @@ -32,6 +32,7 @@ class Http(models.AbstractModel): "server_version": version_info.get('server_version'), "server_version_info": version_info.get('server_version_info'), "name": user.name, + "username": user.login, "company_id": request.env.user.company_id.id if request.session.uid else None, "partner_id": request.env.user.partner_id.id if request.session.uid and request.env.user.partner_id else None, "user_companies": {'current_company': (user.company_id.id, user.company_id.name), 'allowed_companies': [(comp.id, comp.name) for comp in user.company_ids]} if display_switch_company_menu else False, diff --git a/addons/web/static/src/js/views/list_view.js b/addons/web/static/src/js/views/list_view.js index 327edcbb052..f2e4f565a79 100644 --- a/addons/web/static/src/js/views/list_view.js +++ b/addons/web/static/src/js/views/list_view.js @@ -1705,7 +1705,6 @@ var Column = Class.extend({ id: id, tag: tag }); - this.modifiers = attrs.modifiers ? JSON.parse(attrs.modifiers) : {}; delete attrs.modifiers; _.extend(this, attrs); @@ -1732,10 +1731,14 @@ var Column = Class.extend({ if (this.type !== 'integer' && this.type !== 'float' && this.type !== 'monetary') { return {}; } - var aggregation_func = this['group_operator'] || 'sum'; - if (!(aggregation_func in this)) { + + var aggregation_func = (this.sum && 'sum') || (this.avg && 'avg') || + (this.max && 'max') || (this.min && 'min') || this.group_operator; + + if (!aggregation_func) { return {}; } + var C = function (fn, label) { this['function'] = fn; this.label = label; diff --git a/addons/web/static/src/less/form_view.less b/addons/web/static/src/less/form_view.less index 97d8dcccccd..e992ab5a748 100644 --- a/addons/web/static/src/less/form_view.less +++ b/addons/web/static/src/less/form_view.less @@ -55,8 +55,9 @@ > .o_form_field { .o-flex(1, 0, 100px); + position: initial; max-width: 100%; - + > .o_form_input_dropdown > .o_form_input { border-style: none!important; } @@ -251,6 +252,24 @@ cursor: pointer; } } + @media (max-width: @screen-xs-min) { + .o_form_image_controls{ + position: initial; + opacity: 1; + > .fa{ + width: 50%; + padding: 6px; + margin: 0px; + text-align: center; + &.o_select_file_button{ + background: @odoo-brand-optional; + } + &.o_clear_file_button{ + background: @brand-danger; + } + } + } + } &:hover .o_form_image_controls { opacity: 0.8; diff --git a/addons/web/static/src/less/variables.less b/addons/web/static/src/less/variables.less index 5a8120d0d97..0a80aca5db6 100644 --- a/addons/web/static/src/less/variables.less +++ b/addons/web/static/src/less/variables.less @@ -6,7 +6,7 @@ @odoo-brand-secondary: #f0eeee; @odoo-brand-lightsecondary: #e2e2e0; -@odoo-color-pink: #a24689; +@odoo-color-pink: #875A7B; @odoo-main-color-muted: #a8a8a8; @odoo-main-text-color: #4c4c4c; diff --git a/addons/web/static/src/less/views.less b/addons/web/static/src/less/views.less index 9b616d08ada..e5e105b3684 100644 --- a/addons/web/static/src/less/views.less +++ b/addons/web/static/src/less/views.less @@ -20,7 +20,7 @@ background-color: #EB5A46; } .o_tag_color_5 { - background-color: #A24689; + background-color: #875A7B; } .o_tag_color_6 { background-color: #0079BF; diff --git a/addons/web_editor/static/src/js/rte.summernote.js b/addons/web_editor/static/src/js/rte.summernote.js index 11b2dea7d81..1789ed69e7e 100644 --- a/addons/web_editor/static/src/js/rte.summernote.js +++ b/addons/web_editor/static/src/js/rte.summernote.js @@ -155,7 +155,7 @@ renderer.tplPopovers = function (lang, options) { // show dialog box and delete var $imageprop = $('
    '); $imageprop.appendTo($imagePopover.find('.popover-content')); - $(tplIconButton('fa fa-picture-o', { + $(tplIconButton('fa fa-file-image-o', { title: _t('Edit'), event: 'showImageDialog' })).appendTo($imageprop); @@ -216,9 +216,6 @@ eventHandler.modules.popover.button.update = function ($container, oStyle) { if (previous) { $(previous).css({"-webkit-animation-play-state": "", "animation-play-state": "", "-webkit-transition": "", "transition": "", "-webkit-animation": "", "animation": ""}); } - if (oStyle.image) { - $(oStyle.image).css({"-webkit-animation": "none", "animation": "none"}); - } // end fn_boutton_update.call(this, $container, oStyle); @@ -864,7 +861,7 @@ $.summernote.lang.odoo = { size: _t('Font Size') }, image: { - image: _t('Picture'), + image: _t('File / Image'), insert: _t('Insert Image'), resizeFull: _t('Resize Full'), resizeHalf: _t('Resize Half'), diff --git a/addons/web_editor/static/src/js/summernote.js b/addons/web_editor/static/src/js/summernote.js index 365a30b51e6..101c3463e6e 100644 --- a/addons/web_editor/static/src/js/summernote.js +++ b/addons/web_editor/static/src/js/summernote.js @@ -2327,6 +2327,9 @@ eventHandler.detach = function (oLayoutInfo, options) { $('.o_table_handler').remove(); }; +options.icons.image.image = "file-image-o"; +$.summernote.lang['en-US'].image.image = "File / Image"; + return $.summernote; }); diff --git a/addons/web_editor/static/src/less/web_editor.common.less b/addons/web_editor/static/src/less/web_editor.common.less index a2445e55fea..8b1b31e2334 100644 --- a/addons/web_editor/static/src/less/web_editor.common.less +++ b/addons/web_editor/static/src/less/web_editor.common.less @@ -241,7 +241,7 @@ a.o_underline { @gray-white: #fff; @color-alpha: #1cc1a9; -@color-beta: #a24689; +@color-beta: #875A7B; @color-gamma: #BA3C3D; @color-delta: #0D6759; @color-epsilon: #0B2E59; diff --git a/addons/web_planner/static/src/less/web_planner_common.less b/addons/web_planner/static/src/less/web_planner_common.less index 99f6cc9a70e..76a61a7660c 100644 --- a/addons/web_planner/static/src/less/web_planner_common.less +++ b/addons/web_planner/static/src/less/web_planner_common.less @@ -77,7 +77,7 @@ -moz-flex: 0 0 auto; -webkit-flex: 0 0 auto; flex: 0 0 auto; - background-color: #a24689; + background-color: #875A7B; color: white; padding: 15px; @@ -419,7 +419,7 @@ // TROPHY .o_planner_trophy { font-size: 11em; - color: #A24689; + color: #875A7B; margin-top: .5em; -webkit-animation-name: bounceIn; animation-name: bounceIn; @@ -441,7 +441,7 @@ } .odoo_purple, a.btn.odoo_purple, .btn.odoo_purple:hover { - background-color: #A24689; + background-color: #875A7B; color: white; } diff --git a/addons/web_settings_dashboard/static/src/js/dashboard.js b/addons/web_settings_dashboard/static/src/js/dashboard.js index f873e019b89..64cf0f36692 100644 --- a/addons/web_settings_dashboard/static/src/js/dashboard.js +++ b/addons/web_settings_dashboard/static/src/js/dashboard.js @@ -245,7 +245,7 @@ var DashboardApps = Widget.extend({ start: function() { this._super.apply(this, arguments); - if (odoo.db_info && odoo.db_info.server_version_info[5] === 'c') { + if (odoo.db_info && _.last(odoo.db_info.server_version_info) !== 'e') { $(QWeb.render("DashboardEnterprise")).appendTo(this.$el); } }, diff --git a/addons/web_tour/static/src/less/tip.less b/addons/web_tour/static/src/less/tip.less index a8fa17cc65a..c53ad943ff1 100644 --- a/addons/web_tour/static/src/less/tip.less +++ b/addons/web_tour/static/src/less/tip.less @@ -4,7 +4,7 @@ } .o_tooltip { - @o-tip-color: lighten(@odoo-brand-primary, 10%); + @o-tip-color: @odoo-brand-primary; @o-tip-size: 24px; @o-tip-border-width: 2px; @o-tip-arrow-length: 10px; diff --git a/addons/website/data/web_planner_data.xml b/addons/website/data/web_planner_data.xml index a76bca8337b..701ac97897f 100644 --- a/addons/website/data/web_planner_data.xml +++ b/addons/website/data/web_planner_data.xml @@ -12,7 +12,7 @@ Whether you're a beginner or a pro, we have everything you need to plan, create, publish and grow your site, blog or online store.

    - This Planner will help you think about your website and provide tips and ideas to inspire you. There are examp;les and explanations to guide you through the process of creating a top-notch, high quality website that meets all your needs.

    + This Planner will help you think about your website and provide tips and ideas to inspire you. There are examples and explanations to guide you through the process of creating a top-notch, high quality website that meets all your needs.

    The whole process may take a few hours, some discussions with your colleagues and several cups of coffee to go through. But don't worry, you can return to this tool at any time.

    @@ -43,7 +43,7 @@
    -
    Examp;les
    +
    Examples
    • Attract new leads
    • Sell more online
    • @@ -71,7 +71,7 @@
      -
      Examp;les
      +
      Examples
      • Check your references before deciding to work with you
      • See and buy your products
      • @@ -195,13 +195,9 @@
      - Install Apps -

      - Before starting to work on your website, make sure you have all the right applications installed. - It will be much easier for you to organize and create your content if you have all the options activated from the beginning. -

      + Install Features

      - Activate now the apps you need: + Install what you need:

      @@ -214,43 +210,50 @@
      -
      - -

      Blogs

      -

      Attract more leads with a winning content marketing strategy.

      - - Install now - -
      -

      Contact Form

      -

      Integrate a contact form that automatically creates leads in your CRM app.

      +

      Publish a contact form to fill in your CRM pipeline.

      Install now
      - - -
      - -

      Community Builder

      -

      Build and engage with communities with forums, Q&;A, mailing lists.

      - + +

      Blogs

      +

      Attract more visitors with a content marketing strategy.

      +
      Install now

      Jobs

      -

      Organize your job board and promote your job announces easily.

      +

      Promote your job announces and attract new talents.

      Install now
      + +
      + +

      Subscriptions

      +

      Sell subscriptions and manage recurring payments.

      + + Install now + +
      + +
      + +

      Event Tickets

      +

      Organize events and sell tickets online.

      + + Install now + +
      +
    @@ -261,9 +264,9 @@ Your homepage is the most important page of your website. This is where visitors get their first impressions of you. An efficient homepage will prompt them to stay on your site, guide them towards your content, strengthen your company's branding and more.
    Here are some pointers to help you get started.

    -

    Example of Good Homepage

    +

    Click here to unfold a Good Homepage

    -
    +
    @@ -273,7 +276,7 @@

    Find a catchy headline
    Make sure anyone who visits your site understands what it’s about without too much reading.
    - To do that, the visible section of your homepage (above the fold) needs to be simple and straightforward. For examp;le, use pictures of your work, screenshots of your app, a short and catchy hook. + To do that, the visible section of your homepage (above the fold) needs to be simple and straightforward. For example, use pictures of your work, screenshots of your app, a short and catchy hook.

    The Banner or Big Picture building blocks are good choices for that.

    @@ -299,7 +302,7 @@ paragraphs easy to read and avoid walls of text.

    - For examp;le, use pictures to help break up large areas of text by altering + For example, use pictures to help break up large areas of text by altering Text-Image and Image-Text blocks. If you prefer icons, use the Features or Feature Grid blocks.

    @@ -406,10 +409,9 @@ Your Footer

    - Your visitors won't see the footer immediately. Use it for links you don't want in your main menu (e.g. social media, terms and conditions, privacy - terms, etc.). + Your visitors won't see the footer immediately. Use it for links you don't want in your main menu (e.g. social media, terms and conditions, privacy terms, etc.).

    -

    Examp;le of Good Footer

    +

    Click here to unfold a Good Footer

    @@ -489,14 +491,21 @@ for a proper domain.

    -
    +
    How to get a custom domain name?
    -
  • Check its availability or see who currently owns it
  • -
  • Contact a Domain Manager in your country to buy it
  • +
  • Check its availability
  • +
  • Buy it at a provider: + +
  • -
    +
    You own your domain name?
    -
  • Online users: Apply it to your website from your database admin page (see guidelines)
  • +
  • Online users: Apply it to your website from your database admin page
  • Community & Enterprise users: apply a redirection from your Domain Manager platform
  • @@ -565,7 +574,7 @@ Promote

    It’s time to grow your traffic!

    - One way is by optimizing your indexing on search engines like Google naturally. The earliest you show up in the search, the more visitors you get. This strategy called SEO involves several actions. + One way is by optimizing your indexing on search engines like Google naturally, without paying anything. The earliest you show up in the search, the more visitors you get. This strategy called SEO involves several actions.

    @@ -573,7 +582,7 @@

    Use relevant keywords
    - Adding relevant and popular keywords (e.g. business name, location) tends to improve your ranking in search engines and consequently increases your traffic. Do it: + Adding relevant and popular keywords (e.g. business name, location) tends to improve your ranking in search engines and consequently increases your traffic. Do it:

  • through the content of your pages;
  • in their SEO metadata, including tags, title and description.
  • @@ -587,7 +596,7 @@ If you are an artist or a photographer, you can potentially bring in a lot of visitors by referencing your images so they are displayed in image search engines, like Google Images. - To do that, add keywords in picture's description. + To do that, add keywords in picture's description.

    @@ -634,7 +643,7 @@

    You can also pay for a sponsored indexing and place advertisements on web pages that show results from search - engine queries. This is called SEA. + engine queries. This is called SEA.

    @@ -662,91 +671,6 @@
    - - A/B Testing -

    - A/B testing is the process of testing two versions of a page to see which one performs better in reaching your business objectives. -

    -

    Here is a basic guide for your first A/B testing:

    -
    -
    -
    -
    -

    1. Define what to test
    - The main goal is increasing your ROI. Figure out the best page(s) to - test. Here are a few examples of good testing variables: -

    -
  • Call-to-action’s (i.e. button’s) wording, size, color and placement,
  • -
  • Headline or product description,
  • -
  • Form’s length and types of fields,
  • -
  • Product pricing and promotional offers,
  • -
  • Images on landing and product pages,
  • -
  • Amount of text on the page (short vs. long).
  • -
    -
    -
    -
    -
    -
    -

    2. Create the page variation
    -

    -
    -
    -
    -
    -
    -

    3. Configure a Google Analytics account
    - This is necessary in order to record the statistics of your test. -

    -
    -
    -
    -
    -
    -

    4. Create your campaign in Google Analytics
    - Simply follow the wizard by creating a new experiment in the Behavior > - Experiments menu of Google Analytics. - To learn more, take a look at their online help. -

    -
    -
    -
    - - - - - - - - - - - - - - - - - - - -

    Good practices

    Bad practices

    Know how long to run a test before giving up
    - Don't give up too early! Wait for your test results to be significant. Take a look at the statistical - confidence in Google Analytics; It should be at least 95%.
    - Don't give up too late neither because poorly performing variations could cost you revenues.
    Don't test versions at different time periods
    If you - test one version one week and the second the next one, your results won't be - accurate, or worse, wrong!
    Do many A/B tests
    - Don't despair if your first A/B test turns out to be a lemon. The key in optimizing conversion rates is to do a ton of A/B in order to put together all the most effective configurations.
    Don’t surprise regular visitors
    - If you are testing a core part of your website, include only new visitors in - the test. You want to avoid shocking regular visitors, especially because the - variations may not ultimately be implemented.
    Test only one variable
    Try only one variable at a time, otherwise you won't be able to clearly interpret the results of your modifications.
    - - -
    Translate

    @@ -757,12 +681,14 @@

    - - Machine translation
    Free, but quality may vary + + On your own
    -
  • Use a free, automatic machine translation (quality will vary depending on languages)
  • +
  • Free
  • +
  • Time-consuming
  • +
  • Best if you have language skills
  • @@ -771,12 +697,11 @@
    - Human translation
    Pay for professionnal quality + External services
    -
  • Real professional translators will translate all your contents.
  • -
  • Gengo invoices from $0.05 to $0.15 per word, depending on the translator's level of expertise.
  • + Pay services like Gengo to get your content translated by real professional translators. Quality may vary.
    @@ -784,75 +709,92 @@
    Communicate

    - Your website is an important part of your online business strategy, but it shouldn't be - the only one. There are so many ways to communicate with your customers, visitors and + There are so many ways to communicate with your customers, visitors and prospects that it's sometimes difficult to know what to put your energy into. Here are - some advice on what to focus on next. + some advices on what to focus on next.

    -
    -
    -

    Use contact forms

    -
  • Get your opportunities filled up automatically in our integrated CRM application
  • -
  • Qualify your visitors better in order to reach them later with marketing campaigns
  • +
    +
    +

    Use contact forms Install now +

    +
  • Get your opportunities filled up in our integrated CRM application
  • +
  • Qualify your visitors in order to reach them out with marketing campaigns
  • -
    -
    -

    Send mass mails

    -
  • Reach your customers and prospects on a recurring basis
  • +
    +
    +

    Send mass mails Install now +

    + Reach your customers and prospects on a recurring basis
    -
    -
    -

    Organize physical events

    -
  • Meet your existing customers and maintain your relationship
  • -
  • Discuss with propects for a face-to-face sales pitch
  • +
    +
    +

    Publish events Install now +

    + Organize and publish events to meet your prospects and customers
    -
    -
    -

    Create an online Q&A

    -
  • Allows your community to ask and answer questions about their interests
  • -
  • Provide fast, professional and accurate information to your visitors
  • -
  • Reduce the time and resources spent on support
  • - - Install now - -
    -
    -
    -
    -
    -

    Discuss live

    -
  • Improve your conversion from visitors to customers
  • -
  • Learn what your visitors are thinking, what question they have, what problems they encounter
  • +
    +
    +

    Discuss live Install now +

    +
  • Improve your conversion from visitors to customers
  • +
  • Learn what question your customers have, what problems they encounter
  • +
    +
    +
    +
    +
    +

    Launch a forum + + Install now + +

    +
  • Allows your community to ask and answer questions about their interests
  • +
  • Provide fast, professional and accurate information to your visitors
  • +
  • Reduce the time and resources spent on support
  • +
    +
    +
    +
    +
    +

    Publish documentation + + Install now + +

    +
  • Publish multimedias to document your products/services
  • +
  • Manage channels to structure your content
  • diff --git a/addons/website/models/web_planner.py b/addons/website/models/web_planner.py index 47cbe471039..2397a651f32 100644 --- a/addons/website/models/web_planner.py +++ b/addons/website/models/web_planner.py @@ -14,3 +14,10 @@ class WebsitePlanner(models.Model): planner = super(WebsitePlanner, self)._get_planner_application() planner.append(['planner_website', 'Website Planner']) return planner + + @api.model + def _prepare_planner_website_data(self): + values = { + 'company_id': self.env.user.company_id, + } + return values diff --git a/addons/website/static/description/index.html b/addons/website/static/description/index.html index 658c0e95215..ff1eeb6eeab 100644 --- a/addons/website/static/description/index.html +++ b/addons/website/static/description/index.html @@ -1,6 +1,6 @@
    -

    Your enterprise website builder

    +

    Your enterprise website builder

    Easy. Mobile. Open Source.