[MERGE] auth_signup, web, website, portal: clean signin/signup process and customer portal breadcrumb
This merge eases the signin / signup process by rellabelling / adding options / cleaning support of auth tokens. It also improves customer portal by cleaning the breadcrumb now using a simple bootstrap menu. Finally people can now see their sale orders using an access token without having to rely on website_quote. Closes 17022.
This commit is contained in:
@@ -19,7 +19,7 @@
|
||||
<p>Dear ${object.partner_id.name}
|
||||
% set access_action = object.with_context(force_website=True).get_access_action()
|
||||
% set is_online = access_action and access_action['type'] == 'ir.actions.act_url'
|
||||
% set access_url = is_online and '/mail/view?model=account.invoice&res_id=%d' % (object.id) or '/report/pdf/account.report_invoice/' + str(object.id)
|
||||
% set access_url = is_online and object.get_mail_url() or '/report/pdf/account.report_invoice/' + str(object.id)
|
||||
|
||||
% if object.partner_id.parent_id:
|
||||
(<i>${object.partner_id.parent_id.name}</i>)
|
||||
|
||||
@@ -6,7 +6,7 @@ import json
|
||||
import requests
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo.exceptions import AccessDenied
|
||||
from odoo.exceptions import AccessDenied, UserError
|
||||
from odoo.addons.auth_signup.models.res_users import SignupError
|
||||
|
||||
from odoo.addons import base
|
||||
@@ -82,7 +82,7 @@ class ResUsers(models.Model):
|
||||
try:
|
||||
_, login, _ = self.signup(values, token)
|
||||
return login
|
||||
except SignupError:
|
||||
except (SignupError, UserError):
|
||||
raise access_denied_exception
|
||||
|
||||
@api.model
|
||||
|
||||
@@ -6,11 +6,13 @@ import werkzeug
|
||||
from odoo import http, _
|
||||
from odoo.addons.auth_signup.models.res_users import SignupError
|
||||
from odoo.addons.web.controllers.main import ensure_db, Home
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.http import request
|
||||
from odoo.tools import pycompat
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AuthSignupHome(Home):
|
||||
|
||||
@http.route()
|
||||
@@ -33,7 +35,19 @@ class AuthSignupHome(Home):
|
||||
if 'error' not in qcontext and request.httprequest.method == 'POST':
|
||||
try:
|
||||
self.do_signup(qcontext)
|
||||
# Send an account creation confirmation email
|
||||
if qcontext.get('token'):
|
||||
user_sudo = request.env['res.users'].sudo().search([('login', '=', qcontext.get('login'))])
|
||||
template = request.env.ref('auth_signup.mail_template_user_signup_account_created', raise_if_not_found=False)
|
||||
if user_sudo and template:
|
||||
template.sudo().with_context(
|
||||
lang=user_sudo.lang,
|
||||
auth_login=werkzeug.url_encode({'auth_login': user_sudo.email}),
|
||||
password=request.params.get('password')
|
||||
).send_mail(user_sudo.id, force_send=True)
|
||||
return super(AuthSignupHome, self).web_login(*args, **kw)
|
||||
except UserError as e:
|
||||
qcontext['error'] = str(e)
|
||||
except (SignupError, AssertionError) as e:
|
||||
if request.env["res.users"].sudo().search([("login", "=", qcontext.get("login"))]):
|
||||
qcontext["error"] = _("Another user is already registered using this email address.")
|
||||
@@ -57,7 +71,7 @@ class AuthSignupHome(Home):
|
||||
return super(AuthSignupHome, self).web_login(*args, **kw)
|
||||
else:
|
||||
login = qcontext.get('login')
|
||||
assert login, "No login provided."
|
||||
assert login, _("No login provided.")
|
||||
_logger.info(
|
||||
"Password reset attempt for <%s> by user <%s> from %s",
|
||||
login, request.env.user.login, request.httprequest.remote_addr)
|
||||
@@ -86,6 +100,8 @@ class AuthSignupHome(Home):
|
||||
""" Shared helper returning the rendering context for signup and reset password """
|
||||
qcontext = request.params.copy()
|
||||
qcontext.update(self.get_auth_signup_config())
|
||||
if not qcontext.get('token') and request.session.get('auth_signup_token'):
|
||||
qcontext['token'] = request.session.get('auth_signup_token')
|
||||
if qcontext.get('token'):
|
||||
try:
|
||||
# retrieve the user info (name, login or email) corresponding to a signup token
|
||||
@@ -100,8 +116,10 @@ class AuthSignupHome(Home):
|
||||
def do_signup(self, qcontext):
|
||||
""" Shared helper that creates a res.partner out of a token """
|
||||
values = { key: qcontext.get(key) for key in ('login', 'name', 'password') }
|
||||
assert values, "The form was not properly filled in."
|
||||
assert values.get('password') == qcontext.get('confirm_password'), "Passwords do not match; please retype them."
|
||||
if not values:
|
||||
raise UserError(_("The form was not properly filled in."))
|
||||
if values.get('password') != qcontext.get('confirm_password'):
|
||||
raise UserError(_("Passwords do not match; please retype them."))
|
||||
supported_langs = [lang['code'] for lang in request.env['res.lang'].sudo().search_read([], ['code'])]
|
||||
if request.lang in supported_langs:
|
||||
values['lang'] = request.lang
|
||||
|
||||
@@ -91,5 +91,49 @@
|
||||
<field name="user_signature" eval="False"/>
|
||||
</record>
|
||||
|
||||
<!-- Email template for new users that used a signup token -->
|
||||
<record id="mail_template_user_signup_account_created" model="mail.template">
|
||||
<field name="name">Auth Signup: Odoo Account Created</field>
|
||||
<field name="model_id" ref="base.model_res_users"/>
|
||||
<field name="email_from">"${object.company_id.name|safe}" <${(object.company_id.email or user.email)|safe}></field>
|
||||
<field name="email_to">${object.email|safe}</field>
|
||||
<field name="subject">Welcome to ${object.company_id.name}!</field>
|
||||
<field name="body_html" type="html">
|
||||
<div style="padding:0px;width:600px;margin:auto;background: #FFFFFF repeat top /100%;color:#777777">
|
||||
<table cellspacing="0" cellpadding="0" style="width:600px;border-collapse:collapse;background:inherit;color:inherit">
|
||||
<tbody><tr>
|
||||
<td valign="center" width="200" style="padding:10px 10px 10px 5px;font-size: 12px">
|
||||
<img src="/logo.png" style="padding: 0px; margin: 0px; height: auto; width: 80px;" alt="${user.company_id.name}" />
|
||||
</td>
|
||||
</tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div style="padding:0px;width:600px;margin:auto;background: #FFFFFF repeat top /100%;color:#777777">
|
||||
<p>Dear ${object.name},</p>
|
||||
<p>
|
||||
Your account has been successfully created!
|
||||
</p>
|
||||
<p>
|
||||
Your login: ${object.email}
|
||||
<br/>
|
||||
Your password: ${ctx['password']}
|
||||
</p>
|
||||
<p>
|
||||
To gain access to your account, you can use the following link:
|
||||
</p>
|
||||
<div style="text-align: center; margin-top: 16px;">
|
||||
<a href="/web/login?${ctx['auth_login']}" style="padding: 5px 10px; font-size: 12px; line-height: 18px; color: #FFFFFF; border-color:#875A7B; text-decoration: none; display: inline-block; margin-bottom: 0px; font-weight: 400; text-align: center; vertical-align: middle; cursor: pointer; white-space: nowrap; background-image: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius:3px">Go to My Account</a>
|
||||
</div>
|
||||
<p>Best regards,</p>
|
||||
</div>
|
||||
<div style="padding:0px;width:600px;margin:auto; margin-top: 10px; background: #fff repeat top /100%;color:#777777">
|
||||
${user.signature | safe}
|
||||
<p style="font-size: 11px; margin-top: 10px;">
|
||||
<strong>Sent by ${user.company_id.name} using <a href="www.odoo.com" style="text-decoration:none; color: #875A7B;">Odoo</a></strong>
|
||||
</p>
|
||||
</div></field>
|
||||
<field name="user_signature" eval="False"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
|
||||
@@ -2,24 +2,27 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo.tools.safe_eval import safe_eval
|
||||
|
||||
|
||||
class BaseConfigSettings(models.TransientModel):
|
||||
_inherit = 'base.config.settings'
|
||||
|
||||
auth_signup_reset_password = fields.Boolean(string='Enable password reset from Login page')
|
||||
auth_signup_uninvited = fields.Boolean(string='Allow external users to sign up')
|
||||
auth_signup_uninvited = fields.Selection([
|
||||
('b2b', 'On invitation (B2B)'),
|
||||
('b2c', 'Free sign up (B2C)'),
|
||||
], string='Customer Account')
|
||||
auth_signup_template_user_id = fields.Many2one('res.users', string='Template user for new users created through signup')
|
||||
|
||||
@api.model
|
||||
def get_values(self):
|
||||
res = super(BaseConfigSettings, self).get_values()
|
||||
get_param = self.env['ir.config_parameter'].sudo().get_param
|
||||
# we use safe_eval on the result, since the value of the parameter is a nonempty string
|
||||
# the value of the parameter is a nonempty string
|
||||
res.update(
|
||||
auth_signup_reset_password=safe_eval(get_param('auth_signup.reset_password', 'False')),
|
||||
auth_signup_uninvited=safe_eval(get_param('auth_signup.allow_uninvited', 'False')),
|
||||
auth_signup_template_user_id=safe_eval(get_param('auth_signup.template_user_id', 'False')),
|
||||
auth_signup_reset_password=get_param('auth_signup.reset_password', 'False').lower() == 'true',
|
||||
auth_signup_uninvited='b2c' if get_param('auth_signup.allow_uninvited', 'False').lower() == 'true' else 'b2b',
|
||||
auth_signup_template_user_id=get_param('auth_signup.template_user_id', 'False').lower() == 'true',
|
||||
)
|
||||
return res
|
||||
|
||||
@@ -29,7 +32,7 @@ class BaseConfigSettings(models.TransientModel):
|
||||
set_param = self.env['ir.config_parameter'].sudo().set_param
|
||||
# we store the repr of the values, since the value of the parameter is a required string
|
||||
set_param('auth_signup.reset_password', repr(self.auth_signup_reset_password))
|
||||
set_param('auth_signup.allow_uninvited', repr(self.auth_signup_uninvited))
|
||||
set_param('auth_signup.allow_uninvited', repr(self.auth_signup_uninvited == 'b2c'))
|
||||
set_param('auth_signup.template_user_id', repr(self.auth_signup_template_user_id.id))
|
||||
|
||||
@api.multi
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
import random
|
||||
import werkzeug.urls
|
||||
|
||||
from collections import defaultdict
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo import api, exceptions, fields, models, _
|
||||
from odoo.tools import pycompat
|
||||
|
||||
|
||||
@@ -97,6 +98,21 @@ class ResPartner(models.Model):
|
||||
def action_signup_prepare(self):
|
||||
return self.signup_prepare()
|
||||
|
||||
def signup_get_auth_param(self):
|
||||
""" Get a signup token related to the partner if signup is enabled.
|
||||
If the partner already has a user, get the login parameter.
|
||||
"""
|
||||
res = defaultdict(dict)
|
||||
|
||||
allow_signup = self.env['ir.config_parameter'].get_param('auth_signup.allow_uninvited', 'False').lower() == 'true'
|
||||
for partner in self:
|
||||
if allow_signup and not partner.user_ids:
|
||||
partner.signup_prepare()
|
||||
res[partner.id]['auth_signup_token'] = partner.signup_token
|
||||
elif partner.user_ids:
|
||||
res[partner.id]['auth_login'] = partner.user_ids[0].login
|
||||
return res
|
||||
|
||||
@api.multi
|
||||
def signup_cancel(self):
|
||||
return self.write({'signup_token': False, 'signup_type': False, 'signup_expiration': False})
|
||||
@@ -125,11 +141,11 @@ class ResPartner(models.Model):
|
||||
partner = self.search([('signup_token', '=', token)], limit=1)
|
||||
if not partner:
|
||||
if raise_exception:
|
||||
raise SignupError("Signup token '%s' is not valid" % token)
|
||||
raise exceptions.UserError(_("Signup token '%s' is not valid") % token)
|
||||
return False
|
||||
if check_validity and not partner.signup_valid:
|
||||
if raise_exception:
|
||||
raise SignupError("Signup token '%s' is no longer valid" % token)
|
||||
raise exceptions.UserError(_("Signup token '%s' is no longer valid") % token)
|
||||
return False
|
||||
return partner
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ class ResUsers(models.Model):
|
||||
# check that uninvited users may sign up
|
||||
if 'partner_id' not in values:
|
||||
if not literal_eval(get_param('auth_signup.allow_uninvited', 'False')):
|
||||
raise SignupError('Signup is not allowed for uninvited users')
|
||||
raise SignupError(_('Signup is not allowed for uninvited users'))
|
||||
|
||||
assert values.get('login'), "Signup: no login given for new user"
|
||||
assert values.get('partner_id') or values.get('name'), "Signup: no name or partner given for new user"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<odoo>
|
||||
<template id="auth_signup.login" inherit_id="web.login" name="Sign up - Reset Password">
|
||||
<xpath expr="//button[@type='submit']" position="before">
|
||||
<a t-if="signup_enabled" t-attf-href="/web/signup?{{ keep_query() }}" class="btn btn-link pull-right">Sign up</a>
|
||||
<a t-if="signup_enabled" t-attf-href="/web/signup?{{ keep_query() }}" class="btn btn-link pull-right">Don't have an account?</a>
|
||||
<a t-if="reset_password_enabled" t-attf-href="/web/reset_password?{{ keep_query() }}" class="btn btn-link pull-right">Reset Password</a>
|
||||
</xpath>
|
||||
</template>
|
||||
@@ -40,7 +40,7 @@
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
|
||||
<t t-call="auth_signup.fields">
|
||||
<t t-set="only_passwords" t-value="bool(token)"/>
|
||||
<t t-set="only_passwords" t-value="bool(token and not invalid_token)"/>
|
||||
</t>
|
||||
|
||||
<p class="alert alert-danger" t-if="error">
|
||||
@@ -49,7 +49,7 @@
|
||||
<input type="hidden" name="redirect" t-att-value="redirect"/>
|
||||
<input type="hidden" name="token" t-att-value="token"/>
|
||||
<div class="clearfix oe_login_buttons">
|
||||
<a t-attf-href="/web/login?{{ keep_query() }}" class="btn btn-link pull-right">Back to Login</a>
|
||||
<a t-attf-href="/web/login?{{ keep_query() }}" class="btn btn-link pull-right">Already have an account?</a>
|
||||
<button type="submit" class="btn btn-primary pull-left">Sign up</button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -9,14 +9,16 @@
|
||||
<xpath expr="//div[@id='access_rights']" position="after">
|
||||
<div class="col-xs-12 col-md-6 o_setting_box" title="If unchecked, only invited users may sign up.">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="auth_signup_uninvited"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<label string="User Signup" for="auth_signup_uninvited"/>
|
||||
<label for="auth_signup_uninvited"/>
|
||||
<div class="text-muted">
|
||||
Allow external users to sign up from Login page
|
||||
Let your customers log in to see their documents
|
||||
</div>
|
||||
<div class="content-group" attrs="{'invisible': [('auth_signup_uninvited','=',False)]}">
|
||||
<div class="mt8">
|
||||
<field name="auth_signup_uninvited" widget="radio" required="True"/>
|
||||
</div>
|
||||
<div class="content-group" attrs="{'invisible': [('auth_signup_uninvited','=','b2b')]}">
|
||||
<div class="mt16">
|
||||
<button type="object" name="open_template_user" string="Default Access Rights" icon="fa-arrow-right" class="btn-link"/>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<p>Dear ${object.partner_id.name}
|
||||
% set access_action = object.with_context(force_website=True).get_access_action()
|
||||
% set is_online = access_action and access_action['type'] == 'ir.actions.act_url'
|
||||
% set access_url = is_online and '/mail/view?model=account.invoice&res_id=%d' % (object.id) or '/report/pdf/account.report_invoice/' + str(object.id)
|
||||
% set access_url = is_online and object.get_mail_url() or '/report/pdf/account.report_invoice/' + str(object.id)
|
||||
|
||||
% if object.partner_id.parent_id:
|
||||
(<i>${object.partner_id.parent_id.name}</i>)
|
||||
|
||||
@@ -52,7 +52,7 @@ class MailController(http.Controller):
|
||||
return comparison, record, redirect
|
||||
|
||||
@classmethod
|
||||
def _redirect_to_record(cls, model, res_id):
|
||||
def _redirect_to_record(cls, model, res_id, access_token=None):
|
||||
uid = request.session.uid
|
||||
|
||||
# no model / res_id, meaning no possible record -> redirect to login
|
||||
@@ -68,7 +68,6 @@ class MailController(http.Controller):
|
||||
|
||||
# the record has a window redirection: check access rights
|
||||
if uid is not None:
|
||||
record = record_sudo.sudo(uid)
|
||||
if not RecordModel.sudo(uid).check_access_rights('read', raise_exception=False):
|
||||
return cls._redirect_to_messaging()
|
||||
try:
|
||||
@@ -76,7 +75,7 @@ class MailController(http.Controller):
|
||||
except AccessError:
|
||||
return cls._redirect_to_messaging()
|
||||
else:
|
||||
record_action = record.get_access_action()
|
||||
record_action = record_sudo.get_access_action(access_uid=uid)
|
||||
else:
|
||||
record_action = record_sudo.get_access_action()
|
||||
|
||||
@@ -162,15 +161,17 @@ class MailController(http.Controller):
|
||||
return subtypes_list
|
||||
|
||||
@http.route('/mail/view', type='http', auth='none')
|
||||
def mail_action_view(self, model=None, res_id=None, message_id=None):
|
||||
def mail_action_view(self, model=None, res_id=None, message_id=None, access_token=None, **kwargs):
|
||||
""" Generic access point from notification emails. The heuristic to
|
||||
choose where to redirect the user is the following :
|
||||
choose where to redirect the user is the following :
|
||||
|
||||
- find a public URL
|
||||
- if none found
|
||||
- users with a read access are redirected to the document
|
||||
- users without read access are redirected to the Messaging
|
||||
- not logged users are redirected to the login page
|
||||
|
||||
models that have an access_token may apply variations on this.
|
||||
"""
|
||||
if message_id:
|
||||
try:
|
||||
@@ -185,7 +186,7 @@ class MailController(http.Controller):
|
||||
elif res_id and isinstance(res_id, basestring):
|
||||
res_id = int(res_id)
|
||||
|
||||
return self._redirect_to_record(model, res_id)
|
||||
return self._redirect_to_record(model, res_id, access_token)
|
||||
|
||||
@http.route('/mail/follow', type='http', auth='user', methods=['GET'])
|
||||
def mail_action_follow(self, model, res_id, token=None):
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
% set doc_name = 'quotation' if object.state in ('draft', 'sent') else 'order confirmation'
|
||||
% set pay_sign_name = ('require_payment' in object and object.require_payment and 'pay') or 'sign'
|
||||
% set access_name = is_online and object.template_id and object.state in ('draft', 'sent') and 'Accept and %s online' % pay_sign_name or 'View %s' % doc_name
|
||||
% set access_url = is_online and access_action['url'] or ''
|
||||
% set access_url = is_online and object.get_mail_url() or ''
|
||||
|
||||
% if object.partner_id.parent_id:
|
||||
(<i>${object.partner_id.parent_id.name}</i>)
|
||||
|
||||
@@ -293,12 +293,19 @@ class SaleOrder(models.Model):
|
||||
@api.model_cr_context
|
||||
def _init_column(self, column_name):
|
||||
""" Initialize the value of the given column for existing rows.
|
||||
Overridden here because we skip generating unique access tokens
|
||||
for potentially tons of existing sale orders, should they be needed,
|
||||
they will be generated on the fly.
|
||||
|
||||
Overridden here because we need to generate different access tokens
|
||||
and by default _init_column calls the default method once and applies
|
||||
it for every record.
|
||||
"""
|
||||
if column_name != 'access_token':
|
||||
super(SaleOrder, self)._init_column(column_name)
|
||||
else:
|
||||
query = """UPDATE %(table_name)s
|
||||
SET %(column_name)s = md5(md5(random()::varchar || id::varchar) || clock_timestamp()::varchar)::uuid::varchar
|
||||
WHERE %(column_name)s IS NULL
|
||||
""" % {'table_name': self._name, 'column_name': column_name}
|
||||
self.env.cr.execute(query)
|
||||
|
||||
def _generate_access_token(self):
|
||||
for order in self:
|
||||
|
||||
@@ -488,6 +488,9 @@ class Home(http.Controller):
|
||||
if 'error' in request.params and request.params.get('error') == 'access':
|
||||
values['error'] = _('Only employee can access this database. Please contact the administrator.')
|
||||
|
||||
if 'login' not in values and request.session.get('auth_login'):
|
||||
values['login'] = request.session.get('auth_login')
|
||||
|
||||
response = request.render('web.login', values)
|
||||
response.headers['X-Frame-Options'] = 'DENY'
|
||||
return response
|
||||
|
||||
@@ -134,6 +134,13 @@ class Http(models.AbstractModel):
|
||||
first_pass = not hasattr(request, 'website')
|
||||
request.website = None
|
||||
func = None
|
||||
|
||||
# add signup token or login to the session if given
|
||||
if 'auth_signup_token' in request.params:
|
||||
request.session['auth_signup_token'] = request.params['auth_signup_token']
|
||||
if 'auth_login' in request.params:
|
||||
request.session['auth_login'] = request.params['auth_login']
|
||||
|
||||
try:
|
||||
if request.httprequest.method == 'GET' and '//' in request.httprequest.path:
|
||||
new_url = request.httprequest.path.replace('//', '/') + '?' + request.httprequest.query_string
|
||||
|
||||
@@ -53,6 +53,10 @@ class WebsiteConfigSettings(models.TransientModel):
|
||||
has_google_analytics = fields.Boolean("Google Analytics")
|
||||
has_google_analytics_dashboard = fields.Boolean("Google Analytics in Dashboard")
|
||||
has_google_maps = fields.Boolean("Google Maps")
|
||||
auth_signup_uninvited = fields.Selection([
|
||||
('b2b', 'On invitation (B2B)'),
|
||||
('b2c', 'Free sign up (B2C)'),
|
||||
], string='Customer Account')
|
||||
|
||||
@api.onchange('has_google_analytics')
|
||||
def onchange_has_google_analytics(self):
|
||||
@@ -77,12 +81,13 @@ class WebsiteConfigSettings(models.TransientModel):
|
||||
if not self.user_has_groups('website.group_website_designer'):
|
||||
raise AccessDenied()
|
||||
res = super(WebsiteConfigSettings, self).get_values()
|
||||
params = self.env['ir.config_parameter'].sudo()
|
||||
get_param = self.env['ir.config_parameter'].sudo().get_param
|
||||
res.update(
|
||||
has_google_analytics=params.get_param('website.has_google_analytics'),
|
||||
has_google_analytics_dashboard=params.get_param('website.has_google_analytics_dashboard'),
|
||||
has_google_maps=params.get_param('website.has_google_maps'),
|
||||
google_maps_api_key=params.get_param('google_maps_api_key', default=''),
|
||||
auth_signup_uninvited='b2c' if get_param('auth_signup.allow_uninvited', 'False').lower() == 'true' else 'b2b',
|
||||
has_google_analytics=get_param('website.has_google_analytics'),
|
||||
has_google_analytics_dashboard=get_param('website.has_google_analytics_dashboard'),
|
||||
has_google_maps=get_param('website.has_google_maps'),
|
||||
google_maps_api_key=get_param('google_maps_api_key', default=''),
|
||||
)
|
||||
return res
|
||||
|
||||
@@ -90,7 +95,9 @@ class WebsiteConfigSettings(models.TransientModel):
|
||||
if not self.user_has_groups('website.group_website_designer'):
|
||||
raise AccessDenied()
|
||||
super(WebsiteConfigSettings, self).set_values()
|
||||
self.env['ir.config_parameter'].sudo().set_param('website.has_google_analytics', self.has_google_analytics)
|
||||
self.env['ir.config_parameter'].sudo().set_param('website.has_google_analytics_dashboard', self.has_google_analytics_dashboard)
|
||||
self.env['ir.config_parameter'].sudo().set_param('website.has_google_maps', self.has_google_maps)
|
||||
self.env['ir.config_parameter'].sudo().set_param('google_maps_api_key', (self.google_maps_api_key or '').strip())
|
||||
set_param = self.env['ir.config_parameter'].sudo().set_param
|
||||
set_param('auth_signup.allow_uninvited', repr(self.auth_signup_uninvited == 'b2c'))
|
||||
set_param('website.has_google_analytics', self.has_google_analytics)
|
||||
set_param('website.has_google_analytics_dashboard', self.has_google_analytics_dashboard)
|
||||
set_param('website.has_google_maps', self.has_google_maps)
|
||||
set_param('google_maps_api_key', (self.google_maps_api_key or '').strip())
|
||||
|
||||
@@ -147,6 +147,19 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md-6 o_setting_box">
|
||||
<div class="o_setting_left_pane">
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="auth_signup_uninvited"/>
|
||||
<div class="text-muted">
|
||||
Let your customers log in to see their documents
|
||||
</div>
|
||||
<div class="mt8">
|
||||
<field name="auth_signup_uninvited" widget="radio" required="True"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Social Media</h2>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, exceptions, models
|
||||
from werkzeug.urls import url_encode
|
||||
|
||||
|
||||
class AccountInvoice(models.Model):
|
||||
@@ -17,22 +18,36 @@ class AccountInvoice(models.Model):
|
||||
return groups
|
||||
|
||||
@api.multi
|
||||
def get_access_action(self):
|
||||
def get_access_action(self, access_uid=None):
|
||||
""" Instead of the classic form view, redirect to the online invoice for portal users. """
|
||||
self.ensure_one()
|
||||
if self.env.user.share or self.env.context.get('force_website'):
|
||||
user, record = self.env.user, self
|
||||
if access_uid:
|
||||
user = self.env['res.users'].sudo().browse(access_uid)
|
||||
record = self.sudo(user)
|
||||
|
||||
if user.share or self.env.context.get('force_website'):
|
||||
try:
|
||||
self.check_access_rule('read')
|
||||
record.check_access_rule('read')
|
||||
except exceptions.AccessError:
|
||||
pass
|
||||
else:
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'url': '/my/invoices', # No controller /my/invoices/<int>, only a report pdf
|
||||
'url': '/my/invoices?', # No controller /my/invoices/<int>, only a report pdf
|
||||
'target': 'self',
|
||||
'res_id': self.id,
|
||||
}
|
||||
return super(AccountInvoice, self).get_access_action()
|
||||
return super(AccountInvoice, self).get_access_action(access_uid)
|
||||
|
||||
def get_mail_url(self):
|
||||
self.ensure_one()
|
||||
params = {
|
||||
'model': self._name,
|
||||
'res_id': self.id,
|
||||
}
|
||||
params.update(self.partner_id.signup_get_auth_param()[self.partner_id.id])
|
||||
return '/mail/view?' + url_encode(params)
|
||||
|
||||
@api.multi
|
||||
def get_signup_url(self):
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
<odoo>
|
||||
<template id="portal_my_home_menu_invoice" name="Portal layout : invoice menu entries" inherit_id="website_portal.portal_layout" priority="30">
|
||||
<xpath expr="//ul[contains(@class,'o_portal_submenu')]" position="inside">
|
||||
<li t-if="invoice_count" t-att-class="page_name == 'invoice' and 'active'">
|
||||
<a href="/my/invoices">Invoices</a>
|
||||
</li>
|
||||
</xpath>
|
||||
<xpath expr="//ol[contains(@class,'o_portal_submenu')]" position="inside">
|
||||
<li t-if="page_name == 'invoice'" class="active">
|
||||
Invoices
|
||||
</li>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="portal_my_home_invoice" name="Portal My Home : invoice entries" inherit_id="website_portal.portal_my_home" priority="30">
|
||||
<xpath expr="//div[contains(@class,'o_my_home_content')]" position="inside">
|
||||
<h3 t-if="invoice_count" class="page-header">
|
||||
<a href="/my/invoices">Your Invoices and Payments
|
||||
<small class="ml8">
|
||||
<span class='badge'><t t-esc="invoice_count"/></span>
|
||||
</small>
|
||||
<a href="/my/invoices">My Invoices
|
||||
<span class="badge ml8" t-esc="invoice_count"/>
|
||||
</a>
|
||||
</h3>
|
||||
</xpath>
|
||||
@@ -22,7 +20,7 @@
|
||||
<template id="portal_my_invoices" name="My Invoices and Payments">
|
||||
<t t-call="website_portal.portal_layout">
|
||||
<div class="page-header">
|
||||
<span class="h3">Your Invoices</span>
|
||||
<span class="h3">My Invoices</span>
|
||||
<t t-call="website_portal.portal_searchbar"/>
|
||||
</div>
|
||||
<t t-if="not invoices">
|
||||
|
||||
@@ -216,12 +216,13 @@ class BlogPost(models.Model):
|
||||
return result
|
||||
|
||||
@api.multi
|
||||
def get_access_action(self):
|
||||
def get_access_action(self, access_uid=None):
|
||||
""" Instead of the classic form view, redirect to the post on website
|
||||
directly if user is an employee or if the post is published. """
|
||||
self.ensure_one()
|
||||
if self.env.user.share and not self.sudo().website_published:
|
||||
return super(BlogPost, self).get_access_action()
|
||||
user = access_uid and self.env['res.users'].sudo().browse(access_uid) or self.env.user
|
||||
if user.share and not self.sudo().website_published:
|
||||
return super(BlogPost, self).get_access_action(access_uid)
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'url': self.url,
|
||||
|
||||
+444
-472
@@ -193,17 +193,29 @@
|
||||
</xpath>
|
||||
</template>
|
||||
<template id="portal_my_home_menu_lead" name="Portal layout : lead menu entry" inherit_id="website_portal.portal_layout" priority="15">
|
||||
<xpath expr="//ul[contains(@class,'o_portal_submenu')]" position="inside">
|
||||
<t t-if="user.commercial_partner_id.grade_id and lead_count">
|
||||
<li t-att-class="page_name == 'lead' and 'active'">
|
||||
<a href="/my/leads">Leads</a>
|
||||
</li>
|
||||
</t>
|
||||
<t t-if="user.commercial_partner_id.grade_id">
|
||||
<li t-att-class="page_name == 'opportunity' and 'active'">
|
||||
<a href="/my/opportunities">Opportunities</a>
|
||||
</li>
|
||||
</t>
|
||||
<xpath expr="//ol[contains(@class,'o_portal_submenu')]" position="inside">
|
||||
<li t-if="page_name == 'lead' or lead">
|
||||
<a t-if="lead" t-attf-href="/my/leads?{{ keep_query() }}">
|
||||
My Leads
|
||||
</a>
|
||||
<t t-else="">
|
||||
My Leads
|
||||
</t>
|
||||
</li>
|
||||
<li t-if="lead">
|
||||
<span t-field="lead.name"/>
|
||||
</li>
|
||||
<li t-if="page_name == 'opportunity' or opportunity">
|
||||
<a t-if="opportunity" t-attf-href="/my/opportunities?{{ keep_query() }}">
|
||||
My Opportunities
|
||||
</a>
|
||||
<t t-else="">
|
||||
My Opportunities
|
||||
</t>
|
||||
</li>
|
||||
<li t-if="opportunity">
|
||||
<span t-field="opportunity.name"/>
|
||||
</li>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
@@ -211,21 +223,15 @@
|
||||
<xpath expr="//div[contains(@class,'o_my_home_content')]" position="inside">
|
||||
<t t-if="user.commercial_partner_id.grade_id">
|
||||
<h3 t-if="lead_count" class="page-header">
|
||||
<a href="/my/leads">Your Leads
|
||||
<small class="ml8">
|
||||
<span class='badge'><t t-esc="lead_count"/></span>
|
||||
</small>
|
||||
<a href="/my/leads">My Leads
|
||||
<span class="badge ml8" t-esc="lead_count"/>
|
||||
</a>
|
||||
</h3>
|
||||
<h3 class="page-header">
|
||||
<a href="/my/opportunities">Your Opportunities
|
||||
<small class="ml8">
|
||||
<t t-if="opp_count">
|
||||
<span class='badge'><t t-esc="opp_count"/></span>
|
||||
</t>
|
||||
<t t-if="not opp_count">
|
||||
There are currently no opportunities.
|
||||
</t>
|
||||
<a href="/my/opportunities">My Opportunities
|
||||
<span t-if="opp_count" class="badge ml8" t-esc="opp_count"/>
|
||||
<small t-if="not opp_count" class="ml8">
|
||||
There are currently no opportunities.
|
||||
</small>
|
||||
</a>
|
||||
</h3>
|
||||
@@ -236,7 +242,7 @@
|
||||
<template id="portal_my_leads" name="My Leads">
|
||||
<t t-call="website_portal.portal_layout">
|
||||
<h3 class="page-header">
|
||||
Your Leads
|
||||
My Leads
|
||||
<t t-call="website_portal.portal_searchbar"/>
|
||||
</h3>
|
||||
<div t-if="not leads" class="alert alert-warning mt8" role="alert">
|
||||
@@ -271,7 +277,7 @@
|
||||
<template id="portal_my_opportunities" name="My Opportunities">
|
||||
<t t-call="website_portal.portal_layout">
|
||||
<h3 class="page-header">
|
||||
Your Opportunities
|
||||
My Opportunities
|
||||
<t t-call="website_portal.portal_searchbar"/>
|
||||
<button name='new_opp' data-toggle="modal" data-target=".modal_new_opp" class="btn btn-default pull-right mr8"><i class="fa fa-plus fa-lg" /></button>
|
||||
</h3>
|
||||
@@ -344,465 +350,431 @@
|
||||
</template>
|
||||
|
||||
<template id="portal_my_lead" name="My Lead">
|
||||
<t t-call="website.layout">
|
||||
<div id="wrap">
|
||||
<div class="container">
|
||||
<div class="oe_structure">
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<ol class="breadcrumb mt8">
|
||||
<li><a href="/my/home"><i class="fa fa-home"/></a></li>
|
||||
<li><a t-attf-href="/my/leads?#{keep_query()}">My Leads</a></li>
|
||||
<li><span t-field="lead.name"/></li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<span class="pull-right" title="Rating">
|
||||
<t t-foreach="range(1, 4)" t-as="i">
|
||||
<span t-attf-class="fa fa-lg fa-star#{'' if i <= int(lead.priority) else '-o'}"/>
|
||||
</t>
|
||||
</span>
|
||||
<h4>
|
||||
Lead - <span t-field="lead.name"/>
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body row">
|
||||
<div class="col-md-6">
|
||||
<div class="row" t-if="lead.partner_name or lead.email_from or lead.partner_id">
|
||||
<label class="col-xs-4">Customer</label>
|
||||
<address class="col-xs-8">
|
||||
<div>
|
||||
<span t-if="lead.partner_name" itemprop="name" t-field="lead.partner_name" />
|
||||
<span t-if="not lead.partner_name" itemprop="name" t-field="lead.contact_name"/>
|
||||
</div>
|
||||
<div t-if="lead.phone">
|
||||
<span class="fa fa-phone" /> <span itemprop="telephone" t-field="lead.phone" />
|
||||
</div>
|
||||
<div t-if="lead.mobile">
|
||||
<span class="fa fa-mobile" /> <span itemprop="telephone" t-field="lead.mobile" />
|
||||
</div>
|
||||
<div t-if="lead.email_from">
|
||||
<span class="fa fa-envelope"/> <span itemprop="email" t-field="lead.email_from" />
|
||||
</div>
|
||||
</address>
|
||||
</div>
|
||||
<div class="row" t-if="lead.street or lead.street2 or lead.city or lead.state_id or lead.country_id">
|
||||
<label class="col-xs-4">Address</label>
|
||||
<address class="col-xs-8">
|
||||
<div t-if="lead.street"><span t-field="lead.street"/></div>
|
||||
<div t-if="lead.street2"><span t-field="lead.street2"/></div>
|
||||
<div t-if="lead.city or lead.zip">
|
||||
<span t-field="lead.city"/> <span t-field="lead.zip"/>
|
||||
</div>
|
||||
<div t-if="lead.state_id or country_id">
|
||||
<span t-field="lead.state_id"/> <span t-field="lead.country_id"/>
|
||||
</div>
|
||||
</address>
|
||||
</div>
|
||||
<div class="row" t-if="lead.user_id">
|
||||
<label class="col-xs-4">Salesperson</label>
|
||||
<span class="col-xs-8" t-field="lead.user_id" />
|
||||
</div>
|
||||
<div class="row" t-if="lead.team_id">
|
||||
<label class="col-xs-4">Sales Channel</label>
|
||||
<span class="col-xs-8" t-field="lead.team_id" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="row" t-if="lead.date_deadline">
|
||||
<label class="col-xs-4">Expected Closing</label>
|
||||
<span class="col-xs-8" t-field="lead.date_deadline" />
|
||||
</div>
|
||||
<div class="row" groups="!base.group_portal" t-if="lead.tag_ids">
|
||||
<label class="col-xs-4">Tags</label>
|
||||
<span class="col-xs-8">
|
||||
<t t-foreach="lead.tag_ids" t-as="tag">
|
||||
<span class="label label-info" t-esc="tag.name" />
|
||||
</t>
|
||||
</span>
|
||||
</div>
|
||||
<div class="row mt16" t-if="lead.partner_assigned_id">
|
||||
<label class="col-xs-4">Assigned Partner</label>
|
||||
<address class="col-xs-8" t-field="lead.partner_assigned_id" t-options='{"widget": "contact", "fields": ["name", "email", "phone"], "no_marker": True}'/>
|
||||
</div>
|
||||
<div class="row mt16" t-if="lead.campaign_id">
|
||||
<label class="col-xs-4">Campaign</label>
|
||||
<span class="col-xs-8" t-field="lead.campaign_id" />
|
||||
</div>
|
||||
<div class="row" t-if="lead.medium_id">
|
||||
<label class="col-xs-4">Medium</label>
|
||||
<span class="col-xs-8" t-field="lead.medium_id" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<a class="btn btn-primary btn" data-toggle="modal" data-target=".modal_partner_assign_interested"><i class="fa fa-file-text-o"/> I'm interested</a>
|
||||
<a class="btn btn-primary btn" data-toggle="modal" data-target=".modal_partner_assign_desinterested"><i class="fa fa-fw fa-times"/> I'm not interested</a>
|
||||
<div class="modal fade modal_partner_assign_interested" role="form" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<form method="POST" class="js_accept_json modal-content js_website_submit_form interested_partner_assign_form">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input type="hidden" name="lead_id" class="assign_lead_id" t-att-value="lead.id"/>
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
|
||||
<h4 class="modal-title">Lead Feedback</h4>
|
||||
</div>
|
||||
<div class="modal-body" id="sign-dialog">
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="comment">What is the next action? When? What is the expected revenue?</label>
|
||||
<input type="text" name="comment" id="comment" class="form-control comment_interested"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="customer_contacted">Do you have contacted the customer?</label>
|
||||
<input type="checkbox" name="customer_contacted" id="customer_contacted" class="contacted_interested"/>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-danger error_partner_assign_interested" style="display:none;">You need to fill up the next action and contact the customer before accepting the lead</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button t-attf-class="btn btn-primary interested_partner_assign_confirm">Confirm</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade modal_partner_assign_desinterested" role="form" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<form method="POST" class="js_accept_json modal-content js_website_submit_form desinterested_partner_assign_form">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input type="hidden" name="lead_id" class="assign_lead_id" t-att-value="lead.id"/>
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
|
||||
<h4 class="modal-title">Lead Feedback</h4>
|
||||
</div>
|
||||
<div class="modal-body" id="sign-dialog">
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="comment">Why aren't you interested by this lead?</label>
|
||||
<input type="text" name="comment" id="comment" class="form-control comment_desinterested"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="contacted_desinterested">Do you have contacted the customer?</label>
|
||||
<input type="checkbox" name="contacted_desinterested" id="contacted_desinterested" class="contacted_desinterested"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="customer_mark_spam">This lead is a spam</label>
|
||||
<input type="checkbox" name="customer_mark_spam" id="customer_mark_spam" class="customer_mark_spam"/>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-danger error_partner_assign_desinterested" style="display:none;">You need to fill up the next action and contact the customer before accepting the lead</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button t-attf-class="btn btn-primary desinterested_partner_assign_confirm">Confirm</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt32">
|
||||
<div class="col-md-12">
|
||||
<h4><strong>Message and communication history</strong></h4>
|
||||
</div>
|
||||
<div class="col-md-10 col-md-offset-1 mt16">
|
||||
<t t-call="website_mail.message_thread">
|
||||
<t t-set="object" t-value="lead"/>
|
||||
<t t-call="website_portal.portal_layout">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<span class="pull-right" title="Rating">
|
||||
<t t-foreach="range(1, 4)" t-as="i">
|
||||
<span t-attf-class="fa fa-lg fa-star#{'' if i <= int(lead.priority) else '-o'}"/>
|
||||
</t>
|
||||
</div>
|
||||
</span>
|
||||
<h4>
|
||||
Lead - <span t-field="lead.name"/>
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body row">
|
||||
<div class="col-md-6">
|
||||
<div class="row" t-if="lead.partner_name or lead.email_from or lead.partner_id">
|
||||
<label class="col-xs-4">Customer</label>
|
||||
<address class="col-xs-8">
|
||||
<div>
|
||||
<span t-if="lead.partner_name" itemprop="name" t-field="lead.partner_name" />
|
||||
<span t-if="not lead.partner_name" itemprop="name" t-field="lead.contact_name"/>
|
||||
</div>
|
||||
<div t-if="lead.phone">
|
||||
<span class="fa fa-phone" /> <span itemprop="telephone" t-field="lead.phone" />
|
||||
</div>
|
||||
<div t-if="lead.mobile">
|
||||
<span class="fa fa-mobile" /> <span itemprop="telephone" t-field="lead.mobile" />
|
||||
</div>
|
||||
<div t-if="lead.email_from">
|
||||
<span class="fa fa-envelope"/> <span itemprop="email" t-field="lead.email_from" />
|
||||
</div>
|
||||
</address>
|
||||
</div>
|
||||
<div class="row" t-if="lead.street or lead.street2 or lead.city or lead.state_id or lead.country_id">
|
||||
<label class="col-xs-4">Address</label>
|
||||
<address class="col-xs-8">
|
||||
<div t-if="lead.street"><span t-field="lead.street"/></div>
|
||||
<div t-if="lead.street2"><span t-field="lead.street2"/></div>
|
||||
<div t-if="lead.city or lead.zip">
|
||||
<span t-field="lead.city"/> <span t-field="lead.zip"/>
|
||||
</div>
|
||||
<div t-if="lead.state_id or country_id">
|
||||
<span t-field="lead.state_id"/> <span t-field="lead.country_id"/>
|
||||
</div>
|
||||
</address>
|
||||
</div>
|
||||
<div class="row" t-if="lead.user_id">
|
||||
<label class="col-xs-4">Salesperson</label>
|
||||
<span class="col-xs-8" t-field="lead.user_id" />
|
||||
</div>
|
||||
<div class="row" t-if="lead.team_id">
|
||||
<label class="col-xs-4">Sales Channel</label>
|
||||
<span class="col-xs-8" t-field="lead.team_id" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="row" t-if="lead.date_deadline">
|
||||
<label class="col-xs-4">Expected Closing</label>
|
||||
<span class="col-xs-8" t-field="lead.date_deadline" />
|
||||
</div>
|
||||
<div class="row" groups="!base.group_portal" t-if="lead.tag_ids">
|
||||
<label class="col-xs-4">Tags</label>
|
||||
<span class="col-xs-8">
|
||||
<t t-foreach="lead.tag_ids" t-as="tag">
|
||||
<span class="label label-info" t-esc="tag.name" />
|
||||
</t>
|
||||
</span>
|
||||
</div>
|
||||
<div class="row mt16" t-if="lead.partner_assigned_id">
|
||||
<label class="col-xs-4">Assigned Partner</label>
|
||||
<address class="col-xs-8" t-field="lead.partner_assigned_id" t-options='{"widget": "contact", "fields": ["name", "email", "phone"], "no_marker": True}'/>
|
||||
</div>
|
||||
<div class="row mt16" t-if="lead.campaign_id">
|
||||
<label class="col-xs-4">Campaign</label>
|
||||
<span class="col-xs-8" t-field="lead.campaign_id" />
|
||||
</div>
|
||||
<div class="row" t-if="lead.medium_id">
|
||||
<label class="col-xs-4">Medium</label>
|
||||
<span class="col-xs-8" t-field="lead.medium_id" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<a class="btn btn-primary btn" data-toggle="modal" data-target=".modal_partner_assign_interested"><i class="fa fa-file-text-o"/> I'm interested</a>
|
||||
<a class="btn btn-primary btn" data-toggle="modal" data-target=".modal_partner_assign_desinterested"><i class="fa fa-fw fa-times"/> I'm not interested</a>
|
||||
<div class="modal fade modal_partner_assign_interested" role="form" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<form method="POST" class="js_accept_json modal-content js_website_submit_form interested_partner_assign_form">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input type="hidden" name="lead_id" class="assign_lead_id" t-att-value="lead.id"/>
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
|
||||
<h4 class="modal-title">Lead Feedback</h4>
|
||||
</div>
|
||||
<div class="modal-body" id="sign-dialog">
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="comment">What is the next action? When? What is the expected revenue?</label>
|
||||
<input type="text" name="comment" id="comment" class="form-control comment_interested"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="customer_contacted">Do you have contacted the customer?</label>
|
||||
<input type="checkbox" name="customer_contacted" id="customer_contacted" class="contacted_interested"/>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-danger error_partner_assign_interested" style="display:none;">You need to fill up the next action and contact the customer before accepting the lead</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button t-attf-class="btn btn-primary interested_partner_assign_confirm">Confirm</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade modal_partner_assign_desinterested" role="form" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<form method="POST" class="js_accept_json modal-content js_website_submit_form desinterested_partner_assign_form">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input type="hidden" name="lead_id" class="assign_lead_id" t-att-value="lead.id"/>
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
|
||||
<h4 class="modal-title">Lead Feedback</h4>
|
||||
</div>
|
||||
<div class="modal-body" id="sign-dialog">
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="comment">Why aren't you interested by this lead?</label>
|
||||
<input type="text" name="comment" id="comment" class="form-control comment_desinterested"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="contacted_desinterested">Do you have contacted the customer?</label>
|
||||
<input type="checkbox" name="contacted_desinterested" id="contacted_desinterested" class="contacted_desinterested"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="customer_mark_spam">This lead is a spam</label>
|
||||
<input type="checkbox" name="customer_mark_spam" id="customer_mark_spam" class="customer_mark_spam"/>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-danger error_partner_assign_desinterested" style="display:none;">You need to fill up the next action and contact the customer before accepting the lead</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button t-attf-class="btn btn-primary desinterested_partner_assign_confirm">Confirm</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt32">
|
||||
<div class="col-md-12">
|
||||
<h4><strong>Message and communication history</strong></h4>
|
||||
</div>
|
||||
<div class="col-md-10 col-md-offset-1 mt16">
|
||||
<t t-call="website_mail.message_thread">
|
||||
<t t-set="object" t-value="lead"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="portal_my_opportunity" name="My Opportunity">
|
||||
<t t-call="website.layout">
|
||||
<div id="wrap">
|
||||
<div class="container">
|
||||
<div class="oe_structure">
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<ol class="breadcrumb mt8">
|
||||
<li><a href="/my/home"><i class="fa fa-home"/></a></li>
|
||||
<li><a t-attf-href="/my/opportunities?#{keep_query()}">My Opportunities</a></li>
|
||||
<li><span t-field="opportunity.name"/></li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h4 style="display:inline-block">
|
||||
Opportunity - <span t-field="opportunity.name"/>
|
||||
</h4>
|
||||
<span class="btn-group pull-right" role="group">
|
||||
<t t-foreach="stages" t-as="stage">
|
||||
<button type="button" t-att-data="stage.id" t-att-opp="opportunity.id" t-attf-class="pull-right btn btn-sm opp-stage-button #{'btn-default' if opportunity.stage_id.name != stage.name else 'disabled btn-primary'}">
|
||||
<span t-field="stage.name"/>
|
||||
</button>
|
||||
</t>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<strong><span class="text-nowrap" t-esc="opportunity.planned_revenue" t-options="{'widget': 'monetary', 'display_currency': opportunity.company_currency}"/> at <span t-field="opportunity.probability"/>%</strong>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<strong>Expected Closing: <span t-field="opportunity.date_deadline" /></strong>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<span class="pull-right" title="Rating">
|
||||
<t t-foreach="range(1, 4)" t-as="i">
|
||||
<span t-attf-class="fa fa-lg fa-star#{'' if i <= int(opportunity.priority) else '-o'}"/>
|
||||
</t>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="col-md-6">
|
||||
<div>
|
||||
<button type="button" data-toggle="modal" data-target=".modal_edit_contact" class="btn btn-sm btn-primary" style="margin-bottom: 10px;">Edit Contact</button>
|
||||
<div class="modal fade modal_edit_contact" role="form" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<form method="POST" class="js_accept_json modal-content js_website_submit_form edit_contact_form">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input type="hidden" name="opportunity_id" class="opportunity_id" t-att-value="opportunity.id"/>
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
|
||||
<h4 class="modal-title">Edit Contact</h4>
|
||||
</div>
|
||||
<div class="modal-body" id="sign-dialog">
|
||||
<t t-if="opportunity.partner_name">
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="partner_name">Customer Name</label>
|
||||
<input type="text" name="partner_name" class="form-control partner_name" t-att-value="opportunity.partner_name"/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="not opportunity.partner_name">
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="partner_name">Customer Name</label>
|
||||
<input type="text" name="partner_name" class="form-control partner_name" t-att-value="opportunity.contact_name"/>
|
||||
</div>
|
||||
</t>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="phone">Phone</label>
|
||||
<input type="text" name="phone" class="form-control phone" t-att-value="opportunity.phone"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="mobile">Mobile</label>
|
||||
<input type="text" name="mobile" class="form-control mobile" t-att-value="opportunity.mobile"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="email_from">Email</label>
|
||||
<input type="text" name="email_from" class="form-control email_from" t-att-value="opportunity.email_from"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="street">Address</label>
|
||||
<input type="text" name="street" class="form-control street" t-att-value="opportunity.street" placeholder="Street"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="text" name="street2" class="form-control street2" t-att-value="opportunity.street2" placeholder="Street2"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="row">
|
||||
<div class="col-sm-5">
|
||||
<input type="text" name="city" class="form-control city" t-att-value="opportunity.city" placeholder="City"/>
|
||||
</div>
|
||||
<div class="col-sm-5">
|
||||
<select name="state_id" class="form-control state_id">
|
||||
<option>States...</option>
|
||||
<t t-foreach="states or []" t-as="state">
|
||||
<option class="state" t-att-value="state.id" t-att-country="state.country_id.id" t-att-selected="state.id == opportunity.state_id.id">
|
||||
<t t-esc="state.name" />
|
||||
</option>
|
||||
</t>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<input type="text" name="zip" class="form-control zip" t-att-value="opportunity.zip" placeholder="ZIP"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<select name="country_id" class="form-control country_id">
|
||||
<option>Countries...</option>
|
||||
<t t-foreach="countries or []" t-as="country">
|
||||
<option t-att-value="country.id" t-att-selected="country.id == opportunity.country_id.id">
|
||||
<t t-esc="country.name" />
|
||||
</option>
|
||||
</t>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button t-attf-class="btn btn-primary edit_contact_confirm">Confirm</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" t-if="opportunity.partner_name or opportunity.email_from or opportunity.contact_name">
|
||||
<label class="col-xs-4">Customer</label>
|
||||
<address class="col-xs-8">
|
||||
<div>
|
||||
<span t-if="opportunity.partner_name" itemprop="name" t-field="opportunity.partner_name" />
|
||||
<span t-if="not opportunity.partner_name" itemprop="name" t-field="opportunity.contact_name" />
|
||||
</div>
|
||||
<div t-if="opportunity.phone">
|
||||
<span class="fa fa-phone" /> <span itemprop="telephone" t-field="opportunity.phone" />
|
||||
</div>
|
||||
<div t-if="opportunity.mobile">
|
||||
<span class="fa fa-mobile" /> <span itemprop="mobile" t-field="opportunity.mobile" />
|
||||
</div>
|
||||
<div t-if="opportunity.email_from">
|
||||
<span class="fa fa-envelope"/> <span itemprop="email" t-field="opportunity.email_from" />
|
||||
</div>
|
||||
</address>
|
||||
</div>
|
||||
<div class="row" t-if="opportunity.street or opportunity.street2 or opportunity.city or opportunity.state_id or opportunity.country_id">
|
||||
<label class="col-xs-4">Address</label>
|
||||
<address class="col-xs-8">
|
||||
<div t-if="opportunity.street"><span t-field="opportunity.street"/></div>
|
||||
<div t-if="opportunity.street2"><span t-field="opportunity.street2"/></div>
|
||||
<div t-if="opportunity.city or opportunity.zip">
|
||||
<span t-field="opportunity.city"/> <span t-field="opportunity.zip"/>
|
||||
</div>
|
||||
<div t-if="opportunity.state_id or country_id">
|
||||
<span t-field="opportunity.state_id"/> <span t-field="opportunity.country_id"/>
|
||||
</div>
|
||||
</address>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div>
|
||||
<button type="button" data-toggle="modal" data-target=".modal_edit_opp" class="btn btn-sm btn-primary" style="margin-bottom: 10px;">Edit Opportunity</button>
|
||||
<div class="modal fade modal_edit_opp" role="form" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<form method="POST" class="js_accept_json modal-content js_website_submit_form edit_opp_form">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input type="hidden" name="opportunity_id" class="opportunity_id" t-att-value="opportunity.id"/>
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
|
||||
<h4 class="modal-title">Edit Opportunity</h4>
|
||||
</div>
|
||||
<div class="modal-body" id="sign-dialog">
|
||||
<div class="form-group">
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon"><span class="text-nowrap" t-esc="opportunity.company_currency.symbol"/></div>
|
||||
<input type="text" name="planned_revenue" class="form-control planned_revenue" t-att-value="opportunity.planned_revenue" placeholder="Planned Revenue"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-1">
|
||||
<span>at </span>
|
||||
</div>
|
||||
<div class="col-sm-5">
|
||||
<div class="input-group">
|
||||
<input type="text" name="probability" class="form-control probability" t-att-value="opportunity.probability" placeholder="Probability"/>
|
||||
<div class="input-group-addon">%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<label>Priority: </label>
|
||||
<div class="input-group">
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="PriorityRadioOptions" value="0" t-att-checked="opportunity.priority not in ['1','2','3']"/><i class="fa fa-star-o"></i>
|
||||
</label>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="PriorityRadioOptions" value="1" t-att-checked="opportunity.priority == '1'"/><i class="fa fa-star"></i>
|
||||
</label>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="PriorityRadioOptions" value="2" t-att-checked="opportunity.priority == '2'"/><i class="fa fa-star"></i><i class="fa fa-star"></i>
|
||||
</label>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="PriorityRadioOptions" value="3" t-att-checked="opportunity.priority == '3'"/><i class="fa fa-star"></i><i class="fa fa-star"></i><i class="fa fa-star"></i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<label>Deadline: </label>
|
||||
<div class='input-group date'>
|
||||
<input type="text" name="date_deadline" t-att-value="opportunity.date_deadline" class="form-control date_deadline" data-date-format="YYYY-MM-DD" t-att-name="prefix" placeholder="YYYY-MM-DD" />
|
||||
<span class="input-group-addon"><span class="fa fa-calendar"></span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="next_activity">Next Activity</label>
|
||||
<select class="form-control next_activity" name="next_activity">
|
||||
<t t-foreach="activity_types" t-as="activity_type">
|
||||
<option t-att-data="activity_type.id" t-att-selected="activity_type.id == user_activity.activity_type_id.id" t-att-name="activity_type.name" t-att-days="activity_type.days" t-att-summary="activity_type.summary"><t t-esc="activity_type.name"/></option>
|
||||
</t>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="activity_summary">Details Next Activity</label>
|
||||
<textarea row="3" name="activity_summary" class="form-control activity_summary"><t t-esc="user_activity.summary"/></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="activity_date_deadline">Next Activity Date</label>
|
||||
<div class='input-group date'>
|
||||
<input type="text" name="activity_date_deadline" t-att-value="user_activity.date_deadline" class="form-control activity_date_deadline" data-date-format="YYYY-MM-DD" t-att-name="prefix" placeholder="YYYY-MM-DD" />
|
||||
<span class="input-group-addon"><span class="fa fa-calendar"></span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button t-attf-class="btn btn-primary edit_opp_confirm">Confirm</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" t-if="user_activity">
|
||||
<label class="col-xs-4">Next Activity</label>
|
||||
<span class="col-xs-8">
|
||||
<span t-field="user_activity.activity_type_id"/>
|
||||
<span t-if="user_activity.date_deadline"> on </span>
|
||||
<span t-field="user_activity.date_deadline"/>
|
||||
</span>
|
||||
</div>
|
||||
<div class="row" t-if="user_activity">
|
||||
<span class="col-xs-8 col-xs-offset-4" t-field="user_activity.summary"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt32">
|
||||
<div class="col-md-12">
|
||||
<h4><strong>Message and communication history</strong></h4>
|
||||
</div>
|
||||
<div class="col-md-10 col-md-offset-1 mt16">
|
||||
<t t-call="website_mail.message_thread">
|
||||
<t t-set="object" t-value="opportunity"/>
|
||||
<t t-call="website_portal.portal_layout">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h4 style="display:inline-block">
|
||||
Opportunity - <span t-field="opportunity.name"/>
|
||||
</h4>
|
||||
<span class="btn-group pull-right" role="group">
|
||||
<t t-foreach="stages" t-as="stage">
|
||||
<button type="button" t-att-data="stage.id" t-att-opp="opportunity.id" t-attf-class="pull-right btn btn-sm opp-stage-button #{'btn-default' if opportunity.stage_id.name != stage.name else 'disabled btn-primary'}">
|
||||
<span t-field="stage.name"/>
|
||||
</button>
|
||||
</t>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<strong><span class="text-nowrap" t-esc="opportunity.planned_revenue" t-options="{'widget': 'monetary', 'display_currency': opportunity.company_currency}"/> at <span t-field="opportunity.probability"/>%</strong>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<strong>Expected Closing: <span t-field="opportunity.date_deadline" /></strong>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<span class="pull-right" title="Rating">
|
||||
<t t-foreach="range(1, 4)" t-as="i">
|
||||
<span t-attf-class="fa fa-lg fa-star#{'' if i <= int(opportunity.priority) else '-o'}"/>
|
||||
</t>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="col-md-6">
|
||||
<div>
|
||||
<button type="button" data-toggle="modal" data-target=".modal_edit_contact" class="btn btn-sm btn-primary" style="margin-bottom: 10px;">Edit Contact</button>
|
||||
<div class="modal fade modal_edit_contact" role="form" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<form method="POST" class="js_accept_json modal-content js_website_submit_form edit_contact_form">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input type="hidden" name="opportunity_id" class="opportunity_id" t-att-value="opportunity.id"/>
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
|
||||
<h4 class="modal-title">Edit Contact</h4>
|
||||
</div>
|
||||
<div class="modal-body" id="sign-dialog">
|
||||
<t t-if="opportunity.partner_name">
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="partner_name">Customer Name</label>
|
||||
<input type="text" name="partner_name" class="form-control partner_name" t-att-value="opportunity.partner_name"/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="not opportunity.partner_name">
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="partner_name">Customer Name</label>
|
||||
<input type="text" name="partner_name" class="form-control partner_name" t-att-value="opportunity.contact_name"/>
|
||||
</div>
|
||||
</t>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="phone">Phone</label>
|
||||
<input type="text" name="phone" class="form-control phone" t-att-value="opportunity.phone"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="mobile">Mobile</label>
|
||||
<input type="text" name="mobile" class="form-control mobile" t-att-value="opportunity.mobile"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="email_from">Email</label>
|
||||
<input type="text" name="email_from" class="form-control email_from" t-att-value="opportunity.email_from"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="street">Address</label>
|
||||
<input type="text" name="street" class="form-control street" t-att-value="opportunity.street" placeholder="Street"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="text" name="street2" class="form-control street2" t-att-value="opportunity.street2" placeholder="Street2"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="row">
|
||||
<div class="col-sm-5">
|
||||
<input type="text" name="city" class="form-control city" t-att-value="opportunity.city" placeholder="City"/>
|
||||
</div>
|
||||
<div class="col-sm-5">
|
||||
<select name="state_id" class="form-control state_id">
|
||||
<option>States...</option>
|
||||
<t t-foreach="states or []" t-as="state">
|
||||
<option class="state" t-att-value="state.id" t-att-country="state.country_id.id" t-att-selected="state.id == opportunity.state_id.id">
|
||||
<t t-esc="state.name" />
|
||||
</option>
|
||||
</t>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<input type="text" name="zip" class="form-control zip" t-att-value="opportunity.zip" placeholder="ZIP"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<select name="country_id" class="form-control country_id">
|
||||
<option>Countries...</option>
|
||||
<t t-foreach="countries or []" t-as="country">
|
||||
<option t-att-value="country.id" t-att-selected="country.id == opportunity.country_id.id">
|
||||
<t t-esc="country.name" />
|
||||
</option>
|
||||
</t>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button t-attf-class="btn btn-primary edit_contact_confirm">Confirm</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" t-if="opportunity.partner_name or opportunity.email_from or opportunity.contact_name">
|
||||
<label class="col-xs-4">Customer</label>
|
||||
<address class="col-xs-8">
|
||||
<div>
|
||||
<span t-if="opportunity.partner_name" itemprop="name" t-field="opportunity.partner_name" />
|
||||
<span t-if="not opportunity.partner_name" itemprop="name" t-field="opportunity.contact_name" />
|
||||
</div>
|
||||
<div t-if="opportunity.phone">
|
||||
<span class="fa fa-phone" /> <span itemprop="telephone" t-field="opportunity.phone" />
|
||||
</div>
|
||||
<div t-if="opportunity.mobile">
|
||||
<span class="fa fa-mobile" /> <span itemprop="mobile" t-field="opportunity.mobile" />
|
||||
</div>
|
||||
<div t-if="opportunity.email_from">
|
||||
<span class="fa fa-envelope"/> <span itemprop="email" t-field="opportunity.email_from" />
|
||||
</div>
|
||||
</address>
|
||||
</div>
|
||||
<div class="row" t-if="opportunity.street or opportunity.street2 or opportunity.city or opportunity.state_id or opportunity.country_id">
|
||||
<label class="col-xs-4">Address</label>
|
||||
<address class="col-xs-8">
|
||||
<div t-if="opportunity.street"><span t-field="opportunity.street"/></div>
|
||||
<div t-if="opportunity.street2"><span t-field="opportunity.street2"/></div>
|
||||
<div t-if="opportunity.city or opportunity.zip">
|
||||
<span t-field="opportunity.city"/> <span t-field="opportunity.zip"/>
|
||||
</div>
|
||||
<div t-if="opportunity.state_id or country_id">
|
||||
<span t-field="opportunity.state_id"/> <span t-field="opportunity.country_id"/>
|
||||
</div>
|
||||
</address>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div>
|
||||
<button type="button" data-toggle="modal" data-target=".modal_edit_opp" class="btn btn-sm btn-primary" style="margin-bottom: 10px;">Edit Opportunity</button>
|
||||
<div class="modal fade modal_edit_opp" role="form" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<form method="POST" class="js_accept_json modal-content js_website_submit_form edit_opp_form">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<input type="hidden" name="opportunity_id" class="opportunity_id" t-att-value="opportunity.id"/>
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
|
||||
<h4 class="modal-title">Edit Opportunity</h4>
|
||||
</div>
|
||||
<div class="modal-body" id="sign-dialog">
|
||||
<div class="form-group">
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon"><span class="text-nowrap" t-esc="opportunity.company_currency.symbol"/></div>
|
||||
<input type="text" name="planned_revenue" class="form-control planned_revenue" t-att-value="opportunity.planned_revenue" placeholder="Planned Revenue"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-1">
|
||||
<span>at </span>
|
||||
</div>
|
||||
<div class="col-sm-5">
|
||||
<div class="input-group">
|
||||
<input type="text" name="probability" class="form-control probability" t-att-value="opportunity.probability" placeholder="Probability"/>
|
||||
<div class="input-group-addon">%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<label>Priority: </label>
|
||||
<div class="input-group">
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="PriorityRadioOptions" value="0" t-att-checked="opportunity.priority not in ['1','2','3']"/><i class="fa fa-star-o"></i>
|
||||
</label>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="PriorityRadioOptions" value="1" t-att-checked="opportunity.priority == '1'"/><i class="fa fa-star"></i>
|
||||
</label>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="PriorityRadioOptions" value="2" t-att-checked="opportunity.priority == '2'"/><i class="fa fa-star"></i><i class="fa fa-star"></i>
|
||||
</label>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" name="PriorityRadioOptions" value="3" t-att-checked="opportunity.priority == '3'"/><i class="fa fa-star"></i><i class="fa fa-star"></i><i class="fa fa-star"></i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<label>Deadline: </label>
|
||||
<div class='input-group date'>
|
||||
<input type="text" name="date_deadline" t-att-value="opportunity.date_deadline" class="form-control date_deadline" data-date-format="YYYY-MM-DD" t-att-name="prefix" placeholder="YYYY-MM-DD" />
|
||||
<span class="input-group-addon"><span class="fa fa-calendar"></span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="next_activity">Next Activity</label>
|
||||
<select class="form-control next_activity" name="next_activity">
|
||||
<t t-foreach="activity_types" t-as="activity_type">
|
||||
<option t-att-data="activity_type.id" t-att-selected="activity_type.id == user_activity.activity_type_id.id" t-att-name="activity_type.name" t-att-days="activity_type.days" t-att-summary="activity_type.summary"><t t-esc="activity_type.name"/></option>
|
||||
</t>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="activity_summary">Details Next Activity</label>
|
||||
<textarea row="3" name="activity_summary" class="form-control activity_summary"><t t-esc="user_activity.summary"/></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="activity_date_deadline">Next Activity Date</label>
|
||||
<div class='input-group date'>
|
||||
<input type="text" name="activity_date_deadline" t-att-value="user_activity.date_deadline" class="form-control activity_date_deadline" data-date-format="YYYY-MM-DD" t-att-name="prefix" placeholder="YYYY-MM-DD" />
|
||||
<span class="input-group-addon"><span class="fa fa-calendar"></span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button t-attf-class="btn btn-primary edit_opp_confirm">Confirm</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" t-if="user_activity">
|
||||
<label class="col-xs-4">Next Activity</label>
|
||||
<span class="col-xs-8">
|
||||
<span t-field="user_activity.activity_type_id"/>
|
||||
<span t-if="user_activity.date_deadline"> on </span>
|
||||
<span t-field="user_activity.date_deadline"/>
|
||||
</span>
|
||||
</div>
|
||||
<div class="row" t-if="user_activity">
|
||||
<span class="col-xs-8 col-xs-offset-4" t-field="user_activity.summary"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt32">
|
||||
<div class="col-md-12">
|
||||
<h4><strong>Message and communication history</strong></h4>
|
||||
</div>
|
||||
<div class="col-md-10 col-md-offset-1 mt16">
|
||||
<t t-call="website_mail.message_thread">
|
||||
<t t-set="object" t-value="opportunity"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
@@ -776,7 +776,7 @@ class Post(models.Model):
|
||||
return True
|
||||
|
||||
@api.multi
|
||||
def get_access_action(self):
|
||||
def get_access_action(self, access_uid=None):
|
||||
""" Instead of the classic form view, redirect to the post on the website directly """
|
||||
self.ensure_one()
|
||||
return {
|
||||
|
||||
@@ -28,3 +28,7 @@
|
||||
height: 40px;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
@@ -99,42 +99,40 @@
|
||||
<template id="portal_layout" name="Portal Layout">
|
||||
<t t-call="website.layout">
|
||||
<div class="container mt16 o_website_portal">
|
||||
<div class="navbar navbar-default">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#oe-help-navbar-collapse">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="/my/home">
|
||||
My Account
|
||||
</a>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse" id="oe-help-navbar-collapse">
|
||||
<ul class="nav navbar-nav o_portal_submenu">
|
||||
</ul>
|
||||
<div class="oe_structure">
|
||||
<div t-if="not no_breadcrumbs" class="row">
|
||||
<div class="col-sm-12">
|
||||
<ol class="breadcrumb mt8 o_portal_submenu">
|
||||
<li><a href="/my/home"><i class="fa fa-home"/></a></li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="wrap" class='portal-wrap'>
|
||||
<div class="container mb64">
|
||||
<div class="row">
|
||||
<div class="col-md-9 col-lg-8">
|
||||
<t t-raw="0"/>
|
||||
</div>
|
||||
<div id="o_my_sidebar" class="col-md-3 col-lg-offset-1 o_my_sidebar">
|
||||
<div class="o_my_contact" t-if="sales_rep">
|
||||
<t t-call="website_portal.contact"/>
|
||||
<t t-if="my_details">
|
||||
<div class="row">
|
||||
<div t-attf-class="col-md-9 col-lg-8">
|
||||
<t t-raw="0"/>
|
||||
</div>
|
||||
<div class="o_my_details">
|
||||
<h3 class="page-header">Your Details <a href="/my/account" class="btn btn-default btn-xs">Change</a></h3>
|
||||
<div t-field="user.partner_id" t-options='{"widget": "contact", "fields": ["email", "phone", "address", "name"]}'/>
|
||||
</div>
|
||||
<div class="o_my_archives" t-if="archive_groups">
|
||||
<t t-call="website_portal.portal_archive_groups"/>
|
||||
<div id="o_my_sidebar" class="col-md-3 col-lg-offset-1 o_my_sidebar">
|
||||
<div class="o_my_contact" t-if="sales_rep">
|
||||
<t t-call="website_portal.contact"/>
|
||||
</div>
|
||||
<div class="o_my_details">
|
||||
<h3 class="page-header">Your Details <a href="/my/account" class="btn btn-default btn-xs">Change</a></h3>
|
||||
<div t-field="user.partner_id" t-options='{"widget": "contact", "fields": ["email", "phone", "address", "name"]}'/>
|
||||
</div>
|
||||
<div class="o_my_archives" t-if="archive_groups">
|
||||
<t t-call="website_portal.portal_archive_groups"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-raw="0"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
@@ -142,6 +140,7 @@
|
||||
|
||||
<template id="portal_my_home" name="My Portal">
|
||||
<t t-call="website_portal.portal_layout">
|
||||
<t t-set="my_details" t-value="True"/>
|
||||
<div class="o_my_home_content">
|
||||
<div class="oe_structure"></div>
|
||||
</div>
|
||||
|
||||
@@ -2,22 +2,26 @@
|
||||
<odoo>
|
||||
|
||||
<template id="portal_my_home_menu_purchase" name="Portal layout : purchase menu entries" inherit_id="website_portal.portal_layout" priority="25">
|
||||
<xpath expr="//ul[contains(@class,'o_portal_submenu')]" position="inside">
|
||||
<t t-if="purchase_count">
|
||||
<li t-att-class="page_name == 'purchase' and 'active'">
|
||||
<a href="/my/purchase">Purchase Orders</a>
|
||||
</li>
|
||||
</t>
|
||||
<xpath expr="//ol[contains(@class,'o_portal_submenu')]" position="inside">
|
||||
<li t-if="page_name == 'purchase' or purchase_order">
|
||||
<a t-if="purchase_order" t-attf-href="/my/purchase?{{ keep_query() }}">
|
||||
My Purchase Orders
|
||||
</a>
|
||||
<t t-else="">
|
||||
My Purchase Orders
|
||||
</t>
|
||||
</li>
|
||||
<li t-if="purchase_order">
|
||||
<t t-esc="purchase_order.name"/>
|
||||
</li>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="portal_my_home_purchase" name="Portal My Home : purchase entry" inherit_id="website_portal.portal_my_home" priority="25">
|
||||
<xpath expr="//div[contains(@class,'o_my_home_content')]" position="inside">
|
||||
<h3 t-if="purchase_count" class="page-header">
|
||||
<a href="/my/purchase">Your Purchase Orders
|
||||
<small class="ml8">
|
||||
<span class='badge'><t t-esc="purchase_count"/></span>
|
||||
</small>
|
||||
<a href="/my/purchase">My Purchase Orders
|
||||
<span class="badge ml8" t-esc="purchase_count"/>
|
||||
</a>
|
||||
</h3>
|
||||
</xpath>
|
||||
@@ -26,7 +30,7 @@
|
||||
<template id="portal_my_purchase_orders" name="Portal: My Purchase Orders">
|
||||
<t t-call="website_portal.portal_layout">
|
||||
<h3 class="page-header">
|
||||
Your Purchase Orders
|
||||
My Purchase Orders
|
||||
<t t-call="website_portal.portal_searchbar"/>
|
||||
</h3>
|
||||
<table class="table table-hover o_my_status_table">
|
||||
@@ -61,114 +65,94 @@
|
||||
</template>
|
||||
|
||||
<template id="portal_my_purchase_order" name="Portal: My Purchase Order">
|
||||
<t t-call="website.layout">
|
||||
<div id="wrap">
|
||||
<div class="container">
|
||||
<div class="oe_structure">
|
||||
<div class="row">
|
||||
<div class="col-sm-4">
|
||||
<ol class="breadcrumb mt8">
|
||||
<li><a href="/my/home"><i class="fa fa-home"/></a></li>
|
||||
<li>
|
||||
<a t-attf-href="/my/purchase?{{ keep_query()}}">
|
||||
My Purchase Orders
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<t t-esc="order.name"/>
|
||||
</li>
|
||||
</ol>
|
||||
<t t-call="website_portal.portal_layout">
|
||||
<t t-set="purchase_order" t-value="order"/>
|
||||
<div id="optional_placeholder"></div>
|
||||
<div class="container">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h4>
|
||||
<t t-call='website_portal.record_pager'/>
|
||||
Purchase Order
|
||||
<span t-esc="order.name"/>
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="optional_placeholder"></div>
|
||||
<div class="container">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h4>
|
||||
<t t-call='website_portal.record_pager'/>
|
||||
Purchase Order
|
||||
<span t-esc="order.name"/>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="mb8">
|
||||
<strong>Date:</strong> <span t-field="order.date_order" t-options='{"widget": "date"}'/>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<strong>Product</strong>
|
||||
</div>
|
||||
<div class="col-md-2 text-right">
|
||||
<strong>Unit Price</strong>
|
||||
</div>
|
||||
<div class="col-md-2 text-right">
|
||||
<strong>Quantity</strong>
|
||||
</div>
|
||||
<div class="col-md-2 text-right">
|
||||
<strong>Subtotal</strong>
|
||||
</div>
|
||||
</div>
|
||||
<t t-foreach="order.order_line" t-as="ol">
|
||||
<div class="row purchases_vertical_align">
|
||||
<div class="col-md-1 text-center">
|
||||
<img t-att-src="'/web/image/purchase.order.line/%s/product_image/48x48' % ol.id"/>
|
||||
</div>
|
||||
<div id='product_name' class="col-md-5">
|
||||
<span t-esc="ol.product_id.name"/>
|
||||
</div>
|
||||
<div class="col-md-2 text-right">
|
||||
<span t-field="ol.price_unit" t-options='{"widget": "monetary", "display_currency": order.currency_id}'/>
|
||||
</div>
|
||||
<div class="col-md-2 text-right">
|
||||
<span t-esc="ol.product_qty"/>
|
||||
</div>
|
||||
<div class="col-md-2 text-right">
|
||||
<span t-field="ol.price_subtotal" t-options='{"widget": "monetary", "display_currency": order.currency_id}'/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="mb8">
|
||||
<strong>Date:</strong> <span t-field="order.date_order" t-options='{"widget": "date"}'/>
|
||||
</t>
|
||||
|
||||
<hr/>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12 text-right">
|
||||
<div class="row">
|
||||
<div class="col-md-10 text-right">
|
||||
Untaxed Amount:
|
||||
</div>
|
||||
<div class="col-md-2 text-right">
|
||||
<span t-field="order.amount_untaxed" t-options='{"widget": "monetary", "display_currency": order.currency_id}'/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<strong>Product</strong>
|
||||
<div class="col-md-10 text-right">
|
||||
Taxes:
|
||||
</div>
|
||||
<div class="col-md-2 text-right">
|
||||
<strong>Unit Price</strong>
|
||||
</div>
|
||||
<div class="col-md-2 text-right">
|
||||
<strong>Quantity</strong>
|
||||
</div>
|
||||
<div class="col-md-2 text-right">
|
||||
<strong>Subtotal</strong>
|
||||
<span t-field="order.amount_tax" t-options='{"widget": "monetary", "display_currency": order.currency_id}'/>
|
||||
</div>
|
||||
</div>
|
||||
<t t-foreach="order.order_line" t-as="ol">
|
||||
<div class="row purchases_vertical_align">
|
||||
<div class="col-md-1 text-center">
|
||||
<img t-att-src="'/web/image/purchase.order.line/%s/product_image/48x48' % ol.id"/>
|
||||
</div>
|
||||
<div id='product_name' class="col-md-5">
|
||||
<span t-esc="ol.product_id.name"/>
|
||||
</div>
|
||||
<div class="col-md-2 text-right">
|
||||
<span t-field="ol.price_unit" t-options='{"widget": "monetary", "display_currency": order.currency_id}'/>
|
||||
</div>
|
||||
<div class="col-md-2 text-right">
|
||||
<span t-esc="ol.product_qty"/>
|
||||
</div>
|
||||
<div class="col-md-2 text-right">
|
||||
<span t-field="ol.price_subtotal" t-options='{"widget": "monetary", "display_currency": order.currency_id}'/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<hr/>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12 text-right">
|
||||
<div class="row">
|
||||
<div class="col-md-10 text-right">
|
||||
Untaxed Amount:
|
||||
</div>
|
||||
<div class="col-md-2 text-right">
|
||||
<span t-field="order.amount_untaxed" t-options='{"widget": "monetary", "display_currency": order.currency_id}'/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-10 text-right">
|
||||
Taxes:
|
||||
</div>
|
||||
<div class="col-md-2 text-right">
|
||||
<span t-field="order.amount_tax" t-options='{"widget": "monetary", "display_currency": order.currency_id}'/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-10 text-right">
|
||||
<strong>Total:</strong>
|
||||
</div>
|
||||
<div class="col-md-2 text-right">
|
||||
<strong><span t-field="order.amount_total" t-options='{"widget": "monetary", "display_currency": order.currency_id}'/></strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-10 text-right">
|
||||
<strong>Total:</strong>
|
||||
</div>
|
||||
<div class="col-md-2 text-right">
|
||||
<strong><span t-field="order.amount_total" t-options='{"widget": "monetary", "display_currency": order.currency_id}'/></strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_structure mb32"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_structure mb32"/>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import mail
|
||||
from . import main
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import werkzeug
|
||||
|
||||
from odoo.addons.mail.controllers.main import MailController
|
||||
from odoo.exceptions import AccessError
|
||||
from odoo.http import request
|
||||
from odoo.tools.misc import consteq
|
||||
|
||||
|
||||
class MailController(MailController):
|
||||
|
||||
def _redirect_to_record(cls, model, res_id, access_token=None):
|
||||
# If the current user doesn't have access to the sales order, but provided
|
||||
# a valid access token, redirect him to the front-end view.
|
||||
if model == 'sale.order' and res_id and access_token:
|
||||
uid = request.session.uid or request.env.ref('base.public_user').id
|
||||
record_sudo = request.env[model].sudo().browse(res_id).exists()
|
||||
try:
|
||||
record_sudo.sudo(uid).check_access_rights('read')
|
||||
record_sudo.sudo(uid).check_access_rule('read')
|
||||
except AccessError:
|
||||
if record_sudo.access_token and consteq(record_sudo.access_token, access_token):
|
||||
record_action = record_sudo.with_context(
|
||||
force_website=True).get_access_action(uid)
|
||||
if record_action['type'] == 'ir.actions.act_url':
|
||||
return werkzeug.utils.redirect(record_action['url'])
|
||||
return super(MailController, cls)._redirect_to_record(model, res_id, access_token=None)
|
||||
@@ -4,6 +4,7 @@
|
||||
from odoo import http, _
|
||||
from odoo.exceptions import AccessError
|
||||
from odoo.http import request
|
||||
from odoo.tools import consteq
|
||||
|
||||
from odoo.addons.website_portal.controllers.main import website_account, get_records_pager
|
||||
|
||||
@@ -135,21 +136,25 @@ class website_account(website_account):
|
||||
})
|
||||
return request.render("website_portal_sale.portal_my_orders", values)
|
||||
|
||||
@http.route(['/my/orders/<int:order>'], type='http', auth="user", website=True)
|
||||
def orders_followup(self, order=None, **kw):
|
||||
@http.route(['/my/orders/<int:order>'], type='http', auth="public", website=True)
|
||||
def orders_followup(self, order=None, access_token=None, **kw):
|
||||
order = request.env['sale.order'].browse([order])
|
||||
order_sudo = order.sudo()
|
||||
values = {}
|
||||
try:
|
||||
order.check_access_rights('read')
|
||||
order.check_access_rule('read')
|
||||
except AccessError:
|
||||
return request.render("website.403")
|
||||
order_sudo = order.sudo()
|
||||
if not access_token or not consteq(order_sudo.access_token, access_token):
|
||||
return request.render("website.403")
|
||||
values.update({'no_breadcrumbs': True})
|
||||
|
||||
order_invoice_lines = {il.product_id.id: il.invoice_id for il in order_sudo.invoice_ids.mapped('invoice_line_ids')}
|
||||
history = request.session.get('my_orders_history', [])
|
||||
|
||||
values = {
|
||||
values.update({
|
||||
'order': order_sudo,
|
||||
'order_invoice_lines': order_invoice_lines,
|
||||
}
|
||||
values.update(get_records_pager(history, order))
|
||||
})
|
||||
values.update(get_records_pager(history, order_sudo))
|
||||
return request.render("website_portal_sale.orders_followup", values)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, exceptions, fields, models, _
|
||||
from odoo import api, fields, models
|
||||
|
||||
from odoo.exceptions import ValidationError
|
||||
from werkzeug.urls import url_encode
|
||||
import uuid
|
||||
|
||||
|
||||
class SaleOrder(models.Model):
|
||||
@@ -24,26 +25,33 @@ class SaleOrder(models.Model):
|
||||
order.payment_transaction_count = mapped_data.get(order.id, 0)
|
||||
|
||||
@api.multi
|
||||
def get_access_action(self):
|
||||
""" Instead of the classic form view, redirect to the online quote for
|
||||
portal users that have access to a confirmed order. """
|
||||
def get_access_action(self, access_uid=None):
|
||||
""" Instead of the classic form view, redirect to the online order for
|
||||
portal users or if force_website=True in the context. """
|
||||
# TDE note: read access on sales order to portal users granted to followed sales orders
|
||||
self.ensure_one()
|
||||
if self.state == 'cancel' or (self.state == 'draft' and not self.env.context.get('mark_so_as_sent')):
|
||||
return super(SaleOrder, self).get_access_action()
|
||||
if self.env.user.share or self.env.context.get('force_website'):
|
||||
try:
|
||||
self.check_access_rule('read')
|
||||
except exceptions.AccessError:
|
||||
pass
|
||||
else:
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'url': '/my/orders/%s' % self.id,
|
||||
'target': 'self',
|
||||
'res_id': self.id,
|
||||
}
|
||||
return super(SaleOrder, self).get_access_action()
|
||||
return super(SaleOrder, self).get_access_action(access_uid)
|
||||
|
||||
user = self.env['res.users'].sudo().browse(access_uid) if access_uid else self.env.user
|
||||
if user.share or self.env.context.get('force_website'):
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'url': '/my/orders/%s?access_token=%s' % (self.id, self.access_token),
|
||||
'target': 'self',
|
||||
'res_id': self.id,
|
||||
}
|
||||
return super(SaleOrder, self).get_access_action(access_uid)
|
||||
|
||||
def get_mail_url(self):
|
||||
self.ensure_one()
|
||||
params = {
|
||||
'model': self._name,
|
||||
'res_id': self.id,
|
||||
'access_token': self.access_token,
|
||||
}
|
||||
params.update(self.partner_id.signup_get_auth_param()[self.partner_id.id])
|
||||
return '/mail/view?' + url_encode(params)
|
||||
|
||||
@api.multi
|
||||
def _notification_recipients(self, message, groups):
|
||||
|
||||
@@ -7,12 +7,31 @@
|
||||
</template>
|
||||
|
||||
<template id="portal_my_home_menu_sale" name="Portal layout : sales menu entries" inherit_id="website_portal.portal_layout" priority="20">
|
||||
<xpath expr="//ul[contains(@class,'o_portal_submenu')]" position="inside">
|
||||
<li t-if="quotation_count" t-att-class="page_name == 'quote' and 'active'">
|
||||
<a href="/my/quotes">Quotations</a>
|
||||
<xpath expr="//ol[contains(@class,'o_portal_submenu')]" position="inside">
|
||||
<li t-if="page_name == 'quote' or sales_order and sales_order.state == 'sent'">
|
||||
<a t-if="sales_order" t-attf-href="/my/quotes?{{ keep_query() }}">
|
||||
My Quotations
|
||||
</a>
|
||||
<t t-else="">
|
||||
My Quotations
|
||||
</t>
|
||||
</li>
|
||||
<li t-att-class="page_name == 'order' and 'active'">
|
||||
<a href="/my/orders">Sales Orders</a>
|
||||
<li t-if="page_name == 'order' or sales_order and sales_order.state != 'sent'">
|
||||
<a t-if="sales_order" t-attf-href="/my/orders?{{ keep_query() }}">
|
||||
My Sales Orders
|
||||
</a>
|
||||
<t t-else="">
|
||||
My Sales Orders
|
||||
</t>
|
||||
</li>
|
||||
<li t-if="sales_order">
|
||||
<t t-if="sales_order.state == 'sent'">
|
||||
Quotation
|
||||
</t>
|
||||
<t t-if="sales_order.state != 'sent'">
|
||||
Order
|
||||
</t>
|
||||
<t t-esc="sales_order.name"/>
|
||||
</li>
|
||||
</xpath>
|
||||
</template>
|
||||
@@ -20,21 +39,15 @@
|
||||
<template id="portal_my_home_sale" name="Portal My Home : sales entries" inherit_id="website_portal.portal_my_home" priority="20">
|
||||
<xpath expr="//div[contains(@class,'o_my_home_content')]" position="inside">
|
||||
<h3 t-if="quotation_count" class="page-header">
|
||||
<a href="/my/quotes">Your Quotations
|
||||
<small class="ml8">
|
||||
<span class='badge'><t t-esc="quotation_count"/></span>
|
||||
</small>
|
||||
<a href="/my/quotes">My Quotations
|
||||
<span class="badge ml8" t-esc="quotation_count"/>
|
||||
</a>
|
||||
</h3>
|
||||
<h3 class="page-header">
|
||||
<a href="/my/orders">Your Sales Orders
|
||||
<small class="ml8">
|
||||
<t t-if="order_count">
|
||||
<span class='badge'><t t-esc="order_count"/></span>
|
||||
</t>
|
||||
<t t-if="not order_count">
|
||||
There are currently no orders.
|
||||
</t>
|
||||
<a href="/my/orders">My Sales Orders
|
||||
<span t-if="order_count" class="badge ml8" t-esc="order_count"/>
|
||||
<small t-if="not order_count" class="ml8">
|
||||
There are currently no orders.
|
||||
</small>
|
||||
</a>
|
||||
</h3>
|
||||
@@ -44,7 +57,7 @@
|
||||
<template id="portal_my_quotations" name="My Quotations">
|
||||
<t t-call="website_portal.portal_layout">
|
||||
<div class="page-header">
|
||||
<span class="h3">Your Quotations</span>
|
||||
<span class="h3">My Quotations</span>
|
||||
<t t-call="website_portal.portal_searchbar"/>
|
||||
</div>
|
||||
<t t-if="not quotations">
|
||||
@@ -91,7 +104,7 @@
|
||||
<template id="portal_my_orders" name="My Sales Orders">
|
||||
<t t-call="website_portal.portal_layout">
|
||||
<div class="page-header">
|
||||
<span class="h3">Your Sales Orders</span>
|
||||
<span class="h3">My Sales Orders</span>
|
||||
<t t-call="website_portal.portal_searchbar"/>
|
||||
</div>
|
||||
<t t-if="not orders">
|
||||
@@ -142,188 +155,157 @@
|
||||
</template>
|
||||
|
||||
<template id="orders_followup" name="Sales Order">
|
||||
<t t-call="website.layout">
|
||||
<div id="wrap">
|
||||
<div class="container">
|
||||
<div class="oe_structure">
|
||||
<div class="row">
|
||||
<div class="col-sm-4">
|
||||
<ol class="breadcrumb mt8">
|
||||
<li><a href="/my/home"><i class="fa fa-home"/></a></li>
|
||||
<li>
|
||||
<a t-attf-href="/my/#{'quotes' if order.state == 'sent' else 'orders'}?{{ keep_query()}}">
|
||||
<t t-if="order.state == 'sent'">
|
||||
My Quotations
|
||||
</t>
|
||||
<t t-if="order.state != 'sent'">
|
||||
My Sales Orders
|
||||
</t>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<t t-if="order.state == 'sent'">
|
||||
Quotation
|
||||
</t>
|
||||
<t t-if="order.state != 'sent'">
|
||||
Order
|
||||
</t>
|
||||
<t t-esc="order.name"/>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
<t t-call="website_portal.portal_layout">
|
||||
<t t-set="sales_order" t-value="order"/> <!-- rename to ensure breadcrumb compatibility (differentiate with purchase orders) -->
|
||||
<div id="optional_placeholder"></div>
|
||||
<div class="container">
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h4>
|
||||
<t t-call='website_portal.record_pager'/>
|
||||
<t t-if="order.state == 'sent'">
|
||||
Quotation
|
||||
</t>
|
||||
<t t-if="order.state != 'sent'">
|
||||
Order
|
||||
</t>
|
||||
<span t-esc="order.name"/>
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div id="optional_placeholder"></div>
|
||||
<div class="container">
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h4>
|
||||
<t t-call='website_portal.record_pager'/>
|
||||
<t t-if="order.state == 'sent'">
|
||||
Quotation
|
||||
</t>
|
||||
<t t-if="order.state != 'sent'">
|
||||
Order
|
||||
</t>
|
||||
<span t-esc="order.name"/>
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<!-- <hr/> -->
|
||||
<div class="mb8">
|
||||
<strong>Date:</strong> <span t-field="order.create_date" t-options='{"widget": "date"}'/>
|
||||
</div>
|
||||
<div class='row'>
|
||||
<div class="col-md-6">
|
||||
<div>
|
||||
<strong>Invoicing Address</strong>
|
||||
</div>
|
||||
<div>
|
||||
<address t-field="order.partner_invoice_id" t-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": True}'/>
|
||||
</div>
|
||||
<t t-set="invoices" t-value="[i for i in order.invoice_ids if i.state not in ['draft', 'cancel']]"/>
|
||||
<t t-if="invoices">
|
||||
<div>
|
||||
<strong>Invoices</strong>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<!-- <hr/> -->
|
||||
<div class="mb8">
|
||||
<strong>Date:</strong> <span t-field="order.create_date" t-options='{"widget": "date"}'/>
|
||||
</div>
|
||||
<div class='row'>
|
||||
<div class="col-md-6">
|
||||
<div>
|
||||
<strong>Invoicing Address</strong>
|
||||
</div>
|
||||
<div>
|
||||
<address t-field="order.partner_invoice_id" t-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": True}'/>
|
||||
</div>
|
||||
<t t-set="invoices" t-value="[i for i in order.invoice_ids if i.state not in ['draft', 'cancel']]"/>
|
||||
<t t-if="invoices">
|
||||
<div>
|
||||
<strong>Invoices</strong>
|
||||
</div>
|
||||
<div>
|
||||
<t t-foreach="invoices" t-as="i">
|
||||
<t t-set="report_url" t-value="'/my/invoices/%s' % i.id"/>
|
||||
<div>
|
||||
<a t-att-href="report_url"><span class="fa fa-download"/></a>
|
||||
<a t-att-href="report_url"><span t-esc="i.number"/></a>
|
||||
<span class="text-muted" t-field="i.date_invoice"/>
|
||||
<t t-if="i.state == 'paid'">
|
||||
<span class="label label-success orders_label_text_align"><i class="fa fa-fw fa-check"/> Paid</span>
|
||||
</t>
|
||||
<t t-if="i.state != 'paid'">
|
||||
<span class="label label-info orders_label_text_align"><i class="fa fa-fw fa-clock-o"/> Waiting</span>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
<div>
|
||||
<t t-foreach="invoices" t-as="i">
|
||||
<t t-set="report_url" t-value="'/my/invoices/%s' % i.id"/>
|
||||
<div>
|
||||
<a t-att-href="report_url"><span class="fa fa-download"/></a>
|
||||
<a t-att-href="report_url"><span t-esc="i.number"/></a>
|
||||
<span class="text-muted" t-field="i.date_invoice"/>
|
||||
<t t-if="i.state == 'paid'">
|
||||
<span class="label label-success orders_label_text_align"><i class="fa fa-fw fa-check"/> Paid</span>
|
||||
</t>
|
||||
<t t-if="i.state != 'paid'">
|
||||
<span class="label label-info orders_label_text_align"><i class="fa fa-fw fa-clock-o"/> Waiting</span>
|
||||
</t>
|
||||
</div>
|
||||
<div id="shipping_address" class="col-md-6">
|
||||
<div>
|
||||
<strong>Shipping Address</strong>
|
||||
</div>
|
||||
<div>
|
||||
<address t-field="order.partner_shipping_id" t-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": True}'/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<strong>Product</strong>
|
||||
</div>
|
||||
<div class="hidden-sm hidden-xs col-md-2 text-right">
|
||||
<strong>Unit Price</strong>
|
||||
</div>
|
||||
<div class="hidden-sm hidden-xs col-md-2 text-right">
|
||||
<strong>Quantity</strong>
|
||||
</div>
|
||||
<div class="hidden-sm hidden-xs col-md-2 text-right">
|
||||
<strong>Subtotal</strong>
|
||||
</div>
|
||||
</div>
|
||||
<t t-foreach="order.order_line" t-as="ol">
|
||||
<div class="row orders_vertical_align">
|
||||
<div class="visible-lg col-lg-1 text-center">
|
||||
<span t-field="ol.product_image" t-options="{'widget': 'image', 'class': 'img-rounded', 'style':'width: 48px;height:48px'}" />
|
||||
</div>
|
||||
<div id='product_name' class="col-lg-4 col-sm-6 col-xs-4">
|
||||
<span t-esc="ol.product_id.name"/>
|
||||
</div>
|
||||
<div class="col-xs-3 text-right">
|
||||
<span t-field="ol.price_unit" t-options='{"widget": "monetary", "display_currency": order.pricelist_id.currency_id}'/>
|
||||
</div>
|
||||
<div class="col-xs-2 text-right">
|
||||
<t t-if="ol._name == 'sale.order.line'">
|
||||
<span t-esc="ol.product_uom_qty"/>
|
||||
</t>
|
||||
<t t-if="ol._name == 'account.invoice.line'">
|
||||
<span t-esc="ol.quantity"/>
|
||||
</t>
|
||||
</div>
|
||||
<div class="col-sm-2 col-xs-3 text-right">
|
||||
<span t-field="ol.price_subtotal" t-options='{"widget": "monetary", "display_currency": order.pricelist_id.currency_id}'/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
<div id="shipping_address" class="col-md-6">
|
||||
<div>
|
||||
<strong>Shipping Address</strong>
|
||||
</div>
|
||||
<div>
|
||||
<address t-field="order.partner_shipping_id" t-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": True}'/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr/>
|
||||
<hr/>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div>
|
||||
<strong>Contact</strong>
|
||||
</div>
|
||||
<div t-field="order.user_id.partner_id" t-options='{"widget": "contact", "fields": ["email", "phone"]}'/>
|
||||
<br/>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="row">
|
||||
<div class="col-md-9 col-xs-8 text-right">
|
||||
Untaxed Amount:
|
||||
</div>
|
||||
<div class="col-md-3 col-xs-4 text-right">
|
||||
<span t-field="order.amount_untaxed" t-options='{"widget": "monetary", "display_currency": order.pricelist_id.currency_id}'/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-9 col-xs-8 text-right">
|
||||
Taxes:
|
||||
</div>
|
||||
<div class="col-md-3 col-xs-4 text-right">
|
||||
<span t-field="order.amount_tax" t-options='{"widget": "monetary", "display_currency": order.pricelist_id.currency_id}'/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-9 col-xs-8 text-right">
|
||||
<strong>Total:</strong>
|
||||
</div>
|
||||
<div class="col-md-3 col-xs-4 text-right">
|
||||
<strong><span t-field="order.amount_total" t-options='{"widget": "monetary", "display_currency": order.pricelist_id.currency_id}'/></strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<strong>Product</strong>
|
||||
</div>
|
||||
<div class="hidden-sm hidden-xs col-md-2 text-right">
|
||||
<strong>Unit Price</strong>
|
||||
</div>
|
||||
<div class="hidden-sm hidden-xs col-md-2 text-right">
|
||||
<strong>Quantity</strong>
|
||||
</div>
|
||||
<div class="hidden-sm hidden-xs col-md-2 text-right">
|
||||
<strong>Subtotal</strong>
|
||||
</div>
|
||||
</div>
|
||||
<t t-foreach="order.order_line" t-as="ol">
|
||||
<div class="row orders_vertical_align">
|
||||
<div class="visible-lg col-lg-1 text-center">
|
||||
<span t-field="ol.product_image" t-options="{'widget': 'image', 'class': 'img-rounded', 'style':'width: 48px;height:48px'}" />
|
||||
</div>
|
||||
<div id='product_name' class="col-lg-4 col-sm-6 col-xs-4">
|
||||
<span t-esc="ol.product_id.name"/>
|
||||
</div>
|
||||
<div class="col-xs-3 text-right">
|
||||
<span t-field="ol.price_unit" t-options='{"widget": "monetary", "display_currency": order.pricelist_id.currency_id}'/>
|
||||
</div>
|
||||
<div class="col-xs-2 text-right">
|
||||
<t t-if="ol._name == 'sale.order.line'">
|
||||
<span t-esc="ol.product_uom_qty"/>
|
||||
</t>
|
||||
<t t-if="ol._name == 'account.invoice.line'">
|
||||
<span t-esc="ol.quantity"/>
|
||||
</t>
|
||||
</div>
|
||||
<div class="col-sm-2 col-xs-3 text-right">
|
||||
<span t-field="ol.price_subtotal" t-options='{"widget": "monetary", "display_currency": order.pricelist_id.currency_id}'/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<hr/>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div>
|
||||
<strong>Contact</strong>
|
||||
</div>
|
||||
<div t-field="order.user_id.partner_id" t-options='{"widget": "contact", "fields": ["email", "phone"]}'/>
|
||||
<br/>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="row">
|
||||
<div class="col-md-9 col-xs-8 text-right">
|
||||
Untaxed Amount:
|
||||
</div>
|
||||
<div class="col-md-3 col-xs-4 text-right">
|
||||
<span t-field="order.amount_untaxed" t-options='{"widget": "monetary", "display_currency": order.pricelist_id.currency_id}'/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-9 col-xs-8 text-right">
|
||||
Taxes:
|
||||
</div>
|
||||
<div class="col-md-3 col-xs-4 text-right">
|
||||
<span t-field="order.amount_tax" t-options='{"widget": "monetary", "display_currency": order.pricelist_id.currency_id}'/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-9 col-xs-8 text-right">
|
||||
<strong>Total:</strong>
|
||||
</div>
|
||||
<div class="col-md-3 col-xs-4 text-right">
|
||||
<strong><span t-field="order.amount_total" t-options='{"widget": "monetary", "display_currency": order.pricelist_id.currency_id}'/></strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_structure mb32"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="oe_structure mb32"/>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -9,13 +9,18 @@ class Project(models.Model):
|
||||
_inherit = ['project.project']
|
||||
|
||||
@api.multi
|
||||
def get_access_action(self):
|
||||
def get_access_action(self, access_uid=None):
|
||||
""" Instead of the classic form view, redirect to website for portal users
|
||||
that can read the project. """
|
||||
self.ensure_one()
|
||||
if self.env.user.share:
|
||||
user, record = self.env.user, self
|
||||
if access_uid:
|
||||
user = self.env['res.users'].sudo().browse(access_uid)
|
||||
record = self.sudo(user)
|
||||
|
||||
if user.share:
|
||||
try:
|
||||
self.check_access_rule('read')
|
||||
record.check_access_rule('read')
|
||||
except exceptions.AccessError:
|
||||
pass
|
||||
else:
|
||||
@@ -25,7 +30,7 @@ class Project(models.Model):
|
||||
'target': 'self',
|
||||
'res_id': self.id,
|
||||
}
|
||||
return super(Project, self).get_access_action()
|
||||
return super(Project, self).get_access_action(access_uid)
|
||||
|
||||
@api.multi
|
||||
def _notification_recipients(self, message, groups):
|
||||
@@ -47,13 +52,18 @@ class Task(models.Model):
|
||||
task.website_url = '/my/task/%s' % task.id
|
||||
|
||||
@api.multi
|
||||
def get_access_action(self):
|
||||
def get_access_action(self, access_uid=None):
|
||||
""" Instead of the classic form view, redirect to website for portal users
|
||||
that can read the task. """
|
||||
self.ensure_one()
|
||||
if self.env.user.share:
|
||||
user, record = self.env.user, self
|
||||
if access_uid:
|
||||
user = self.env['res.users'].sudo().browse(access_uid)
|
||||
record = self.sudo(user)
|
||||
|
||||
if user.share:
|
||||
try:
|
||||
self.check_access_rule('read')
|
||||
record.check_access_rule('read')
|
||||
except exceptions.AccessError:
|
||||
pass
|
||||
else:
|
||||
@@ -63,7 +73,7 @@ class Task(models.Model):
|
||||
'target': 'self',
|
||||
'res_id': self.id,
|
||||
}
|
||||
return super(Task, self).get_access_action()
|
||||
return super(Task, self).get_access_action(access_uid)
|
||||
|
||||
@api.multi
|
||||
def _notification_recipients(self, message, groups):
|
||||
|
||||
@@ -1,12 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<template id="portal_layout" name="Portal layout: project menu entry" inherit_id="website_portal.portal_layout" priority="40">
|
||||
<xpath expr="//ul[contains(@class,'o_portal_submenu')]" position="inside">
|
||||
<li t-if="project_count" t-att-class="page_name == 'project' and 'active'">
|
||||
<a href="/my/projects">Projects</a>
|
||||
<xpath expr="//ol[contains(@class,'o_portal_submenu')]" position="inside">
|
||||
<li t-if="page_name == 'project' or project">
|
||||
<a t-if="project" t-attf-href="/my/projects?{{ keep_query() }}">
|
||||
Projects
|
||||
</a>
|
||||
<t t-else="">
|
||||
Projects
|
||||
</t>
|
||||
</li>
|
||||
<li t-if="task_count" t-att-class="page_name == 'task' and 'active'">
|
||||
<a href="/my/tasks">Tasks</a>
|
||||
<li t-if="project">
|
||||
<t t-esc="project.name"/>
|
||||
</li>
|
||||
<li t-if="page_name == 'task' or task">
|
||||
<a t-if="task" t-attf-href="/my/tasks?{{ keep_query() }}">
|
||||
My Tasks
|
||||
</a>
|
||||
<t t-else="">
|
||||
My Tasks
|
||||
</t>
|
||||
</li>
|
||||
<li t-if="task">
|
||||
<span t-field="task.name"/>
|
||||
</li>
|
||||
</xpath>
|
||||
</template>
|
||||
@@ -14,17 +30,13 @@
|
||||
<template id="portal_my_home" name="Portal My Home: project entries" inherit_id="website_portal.portal_my_home" priority="40">
|
||||
<xpath expr="//div[contains(@class,'o_my_home_content')]" position="inside">
|
||||
<h3 t-if="project_count" class="page-header">
|
||||
<a href="/my/projects">Your Projects
|
||||
<small class="ml8">
|
||||
<span class='badge'><t t-esc="project_count"/></span>
|
||||
</small>
|
||||
<a href="/my/projects">My Projects
|
||||
<span class="badge ml8" t-esc="project_count"/>
|
||||
</a>
|
||||
</h3>
|
||||
<h3 t-if="task_count" class="page-header">
|
||||
<a href="/my/tasks">Your Tasks
|
||||
<small class="ml8">
|
||||
<span class='badge'><t t-esc="task_count"/></span>
|
||||
</small>
|
||||
<a href="/my/tasks">My Tasks
|
||||
<span class="badge ml8" t-esc="task_count"/>
|
||||
</a>
|
||||
</h3>
|
||||
</xpath>
|
||||
@@ -34,7 +46,7 @@
|
||||
<t t-call="website_portal.portal_layout">
|
||||
<div class="row">
|
||||
<h3 class="page-header">
|
||||
Your Projects
|
||||
My Projects
|
||||
<t t-call="website_portal.portal_searchbar"/>
|
||||
</h3>
|
||||
</div>
|
||||
@@ -73,80 +85,63 @@
|
||||
</template>
|
||||
|
||||
<template id="my_project" name="My Project">
|
||||
<t t-call="website.layout">
|
||||
<div id="wrap">
|
||||
<div class="container">
|
||||
<div class="oe_structure">
|
||||
<div class="row">
|
||||
<div class="col-sm-4">
|
||||
<ol class="breadcrumb mt8">
|
||||
<li><a href="/my/home"><i class="fa fa-home"/></a></li>
|
||||
<li><a t-attf-href="/my/projects?#{keep_query()}">My Projects</a></li>
|
||||
<li><span t-field="project.name"/></li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h4>
|
||||
<t t-call='website_portal.record_pager'/>
|
||||
Project - <span t-field="project.name"/>
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class='row'>
|
||||
<div class="col-md-6">
|
||||
<t t-if="project.partner_id">
|
||||
<strong>Customer</strong>
|
||||
<div t-if="project.partner_id">
|
||||
<address t-field="project.partner_id" t-field-options='{"widget": "contact", "fields": ["name", "email"]}'/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="project.user_id">
|
||||
<strong>Project Manager</strong>
|
||||
<div>
|
||||
<address t-field="project.user_id" t-field-options='{"widget": "contact", "fields": ["name", "email", "phone"]}'/>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="pull-right">
|
||||
<a t-attf-href="/my/tasks?filterby=#{project.id}" class="btn btn-default btn-lg">
|
||||
<span class="fa fa-tasks" />
|
||||
<span t-esc="project.task_count" />
|
||||
<span t-field="project.label_tasks" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt32">
|
||||
<div class="col-md-12">
|
||||
<h4><strong>Message and communication history</strong></h4>
|
||||
</div>
|
||||
<div class="col-md-10 col-md-offset-1 mt16">
|
||||
<t t-call="website_mail.message_thread">
|
||||
<t t-set="object" t-value="project"/>
|
||||
</t>
|
||||
<t t-call="website_portal.portal_layout">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h4>
|
||||
<t t-call='website_portal.record_pager'/>
|
||||
Project - <span t-field="project.name"/>
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class='row'>
|
||||
<div class="col-md-6">
|
||||
<t t-if="project.partner_id">
|
||||
<strong>Customer</strong>
|
||||
<div t-if="project.partner_id">
|
||||
<address t-field="project.partner_id" t-field-options='{"widget": "contact", "fields": ["name", "email"]}'/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="project.user_id">
|
||||
<strong>Project Manager</strong>
|
||||
<div>
|
||||
<address t-field="project.user_id" t-field-options='{"widget": "contact", "fields": ["name", "email", "phone"]}'/>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="pull-right">
|
||||
<a t-attf-href="/my/tasks?filterby=#{project.id}" class="btn btn-default btn-lg">
|
||||
<span class="fa fa-tasks" />
|
||||
<span t-esc="project.task_count" />
|
||||
<span t-field="project.label_tasks" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt32">
|
||||
<div class="col-md-12">
|
||||
<h4><strong>Message and communication history</strong></h4>
|
||||
</div>
|
||||
<div class="col-md-10 col-md-offset-1 mt16">
|
||||
<t t-call="website_mail.message_thread">
|
||||
<t t-set="object" t-value="project"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="my_tasks" name="My Tasks">
|
||||
<t t-call="website_portal.portal_layout">
|
||||
<h3 class="page-header">
|
||||
Your Tasks
|
||||
My Tasks
|
||||
<t t-call="website_portal.portal_searchbar"/>
|
||||
</h3>
|
||||
<t t-if="not tasks">
|
||||
@@ -183,82 +178,65 @@
|
||||
</template>
|
||||
|
||||
<template id="my_task" name="My Task">
|
||||
<t t-call="website.layout">
|
||||
<div id="wrap">
|
||||
<div class="container">
|
||||
<div class="oe_structure">
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<ol class="breadcrumb mt8">
|
||||
<li><a href="/my/home"><i class="fa fa-home"/></a></li>
|
||||
<li><a t-attf-href="/my/tasks?#{keep_query()}">My Tasks</a></li>
|
||||
<li><span t-field="task.name"/></li>
|
||||
</ol>
|
||||
<t t-call="website_portal.portal_layout">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h4>
|
||||
<t t-call='website_portal.record_pager'/>
|
||||
Task - <span t-field="task.name"/>
|
||||
<span t-field="task.stage_id.name" class="pull-right label label-info" title="Current stage of this task"/>
|
||||
<a class="btn btn-info" t-att-href="'/web#return_label=Website&model=project.task&id=%s&view_type=form' % (task.id)" groups="project.group_project_user">Edit Task</a>
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="mb8" t-if="user.partner_id.id in task.sudo().project_id.message_partner_ids.ids">
|
||||
<strong>Project:</strong> <a t-attf-href="/my/project/#{task.project_id.id}" t-field="task.project_id.name"/>
|
||||
</div>
|
||||
|
||||
<div class='row'>
|
||||
<div class="col-md-6">
|
||||
<div class="mb8">
|
||||
<strong>Date:</strong> <span t-field="task.create_date" t-field-options='{"widget": "date"}'/>
|
||||
</div>
|
||||
|
||||
<div t-if="task.user_id">
|
||||
<strong>Assigned to</strong>
|
||||
<div>
|
||||
<address t-field="task.user_id" t-field-options='{"widget": "contact", "fields": ["name", "email", "phone"]}'/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb8" t-if="task.date_deadline">
|
||||
<strong>Deadline:</strong> <span t-field="task.date_deadline" t-field-options='{"widget": "date"}'/>
|
||||
</div>
|
||||
<div t-if="task.partner_id">
|
||||
<strong>Reported by</strong>
|
||||
<div>
|
||||
<address t-field="task.partner_id" t-field-options='{"widget": "contact", "fields": ["name", "email"]}'/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h4>
|
||||
<t t-call='website_portal.record_pager'/>
|
||||
Task - <span t-field="task.name"/>
|
||||
<span t-field="task.stage_id.name" class="pull-right label label-info" title="Current stage of this task"/>
|
||||
<a class="btn btn-info" t-att-href="'/web#return_label=Website&model=project.task&id=%s&view_type=form' % (task.id)" groups="project.group_project_user">Edit Task</a>
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="mb8" t-if="user.partner_id.id in task.sudo().project_id.message_partner_ids.ids">
|
||||
<strong>Project:</strong> <a t-attf-href="/my/project/#{task.project_id.id}" t-field="task.project_id.name"/>
|
||||
</div>
|
||||
|
||||
<div class='row'>
|
||||
<div class="col-md-6">
|
||||
<div class="mb8">
|
||||
<strong>Date:</strong> <span t-field="task.create_date" t-field-options='{"widget": "date"}'/>
|
||||
</div>
|
||||
|
||||
<div t-if="task.user_id">
|
||||
<strong>Assigned to</strong>
|
||||
<div>
|
||||
<address t-field="task.user_id" t-field-options='{"widget": "contact", "fields": ["name", "email", "phone"]}'/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb8" t-if="task.date_deadline">
|
||||
<strong>Deadline:</strong> <span t-field="task.date_deadline" t-field-options='{"widget": "date"}'/>
|
||||
</div>
|
||||
<div t-if="task.partner_id">
|
||||
<strong>Reported by</strong>
|
||||
<div>
|
||||
<address t-field="task.partner_id" t-field-options='{"widget": "contact", "fields": ["name", "email"]}'/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container" t-if="task.description">
|
||||
<p t-field="task.description" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt32">
|
||||
<div class="col-md-12">
|
||||
<h4><strong>Message and communication history</strong></h4>
|
||||
</div>
|
||||
<div class="col-md-10 col-md-offset-1 mt16">
|
||||
<t t-call="website_mail.message_thread">
|
||||
<t t-set="object" t-value="task"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container" t-if="task.description">
|
||||
<p t-field="task.description" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt32">
|
||||
<div class="col-md-12">
|
||||
<h4><strong>Message and communication history</strong></h4>
|
||||
</div>
|
||||
<div class="col-md-10 col-md-offset-1 mt16">
|
||||
<t t-call="website_mail.message_thread">
|
||||
<t t-set="object" t-value="task"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
</odoo>
|
||||
|
||||
@@ -22,13 +22,18 @@ class Issue(models.Model):
|
||||
issue.attachment_ids = list(set(attachment_ids) - set(message_attachment_ids))
|
||||
|
||||
@api.multi
|
||||
def get_access_action(self):
|
||||
def get_access_action(self, access_uid=None):
|
||||
""" Instead of the classic form view, redirect to website for portal users
|
||||
that can read the issue. """
|
||||
self.ensure_one()
|
||||
if self.env.user.share:
|
||||
user, record = self.env.user, self
|
||||
if access_uid:
|
||||
user = self.env['res.users'].sudo().browse(access_uid)
|
||||
record = self.sudo(user)
|
||||
|
||||
if user.share:
|
||||
try:
|
||||
self.check_access_rule('read')
|
||||
record.check_access_rule('read')
|
||||
except exceptions.AccessError:
|
||||
pass
|
||||
else:
|
||||
@@ -38,7 +43,7 @@ class Issue(models.Model):
|
||||
'target': 'self',
|
||||
'res_id': self.id,
|
||||
}
|
||||
return super(Issue, self).get_access_action()
|
||||
return super(Issue, self).get_access_action(access_uid)
|
||||
|
||||
@api.multi
|
||||
def _notification_recipients(self, message, groups):
|
||||
|
||||
@@ -2,9 +2,17 @@
|
||||
<odoo>
|
||||
|
||||
<template id="portal_layout" name="Portal layout: Issues menu entry" inherit_id="website_portal.portal_layout" priority="45">
|
||||
<xpath expr="//ul[contains(@class,'o_portal_submenu')]" position="inside">
|
||||
<li t-if="issue_count" t-att-class="page_name == 'issue' and 'active'">
|
||||
<a href="/my/issues">Issues</a>
|
||||
<xpath expr="//ol[contains(@class,'o_portal_submenu')]" position="inside">
|
||||
<li t-if="page_name == 'issue' or issue">
|
||||
<a t-if="issue" t-attf-href="/my/issues?{{ keep_query() }}">
|
||||
My Issues
|
||||
</a>
|
||||
<t t-else="">
|
||||
My Issues
|
||||
</t>
|
||||
</li>
|
||||
<li t-if="issue">
|
||||
Issue <span t-esc="issue.id"/>
|
||||
</li>
|
||||
</xpath>
|
||||
</template>
|
||||
@@ -12,10 +20,8 @@
|
||||
<template id="portal_my_home" name="Portal My Home: Issue entries" inherit_id="website_portal.portal_my_home" priority="45">
|
||||
<xpath expr="//div[contains(@class,'o_my_home_content')]" position="inside">
|
||||
<h3 t-if="issue_count" class="page-header">
|
||||
<a href="/my/issues">Your Issues
|
||||
<small class="ml8">
|
||||
<span class='badge'><t t-esc="issue_count"/></span>
|
||||
</small>
|
||||
<a href="/my/issues">My Issues
|
||||
<span class="badge ml8" t-esc="issue_count"/>
|
||||
</a>
|
||||
</h3>
|
||||
</xpath>
|
||||
@@ -24,7 +30,7 @@
|
||||
<template id="my_issues" name="My Issues">
|
||||
<t t-call="website_portal.portal_layout">
|
||||
<h3 class="page-header">
|
||||
Your Issues
|
||||
My Issues
|
||||
<t t-call="website_portal.portal_searchbar"/>
|
||||
</h3>
|
||||
<t t-if="not issues">
|
||||
@@ -61,87 +67,70 @@
|
||||
</template>
|
||||
|
||||
<template id="my_issues_issue" name="My Issue">
|
||||
<t t-call="website.layout">
|
||||
<div id="wrap">
|
||||
<div class="container">
|
||||
<div class="oe_structure">
|
||||
<div class="row">
|
||||
<div class="col-sm-4">
|
||||
<ol class="breadcrumb mt8">
|
||||
<li><a href="/my/home"><i class="fa fa-home"/></a></li>
|
||||
<li><a t-attf-href="/my/issues?#{keep_query()}">My Issues</a></li>
|
||||
<li>Issue <span t-esc="issue.id"/></li>
|
||||
</ol>
|
||||
</div>
|
||||
<t t-call="website_portal.portal_layout">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h4>
|
||||
<t t-call='website_portal.record_pager'/>
|
||||
Issue <span t-esc="issue.id"/> - <span t-field="issue.name"/>
|
||||
<a class="btn btn-info" t-att-href="'/web#return_label=Website&model=project.issue&id=%s&view_type=form' % (issue.id)" groups="project.group_project_user">Edit Issue</a>
|
||||
<span t-field="issue.stage_id.name" class="pull-right label label-info" title="Current stage of this issue"/>
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h4>
|
||||
<t t-call='website_portal.record_pager'/>
|
||||
Issue <span t-esc="issue.id"/> - <span t-field="issue.name"/>
|
||||
<a class="btn btn-info" t-att-href="'/web#return_label=Website&model=project.issue&id=%s&view_type=form' % (issue.id)" groups="project.group_project_user">Edit Issue</a>
|
||||
<span t-field="issue.stage_id.name" class="pull-right label label-info" title="Current stage of this issue"/>
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="mb8">
|
||||
<strong>Date:</strong> <span t-field="issue.create_date" t-options='{"widget": "date"}'/>
|
||||
</div>
|
||||
<div class='row'>
|
||||
<div class="col-md-6" t-if="issue.user_id">
|
||||
<strong>Assigned to</strong>
|
||||
<div>
|
||||
<address t-field="issue.user_id" t-options='{"widget": "contact", "fields": ["name", "email", "phone"], "no_marker": True}'/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<strong>Reported by</strong>
|
||||
<div t-if="issue.partner_id">
|
||||
<address t-field="issue.partner_id" t-options='{"widget": "contact", "fields": ["name", "email"], "no_marker": True}'/>
|
||||
</div>
|
||||
</div>
|
||||
<t t-if="issue.description">
|
||||
<div class="col-md-12">
|
||||
<strong>Description</strong>
|
||||
<div t-field="issue.description"/>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<div t-if="issue.attachment_ids" class='panel-footer'>
|
||||
<strong>
|
||||
<t t-if='len(issue.attachment_ids)>1'>Attachments</t>
|
||||
<t t-else="">Attachment</t>
|
||||
</strong>
|
||||
<div class='row'>
|
||||
<div class="col-md-2 col-sm-3 text-center oe_attachments" t-foreach='issue.attachment_ids' t-as='attachment'>
|
||||
<a t-attf-href="/web/content/#{attachment.id}?download=true" target="_blank">
|
||||
<div class='oe_attachment_embedded o_image o_image_small' t-att-title="attachment.name" t-att-data-mimetype="attachment.mimetype" t-attf-data-src="/web/image/#{attachment.id}/50x40"/>
|
||||
<div class='oe_attachment_name'><t t-esc='attachment.name' /></div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="mb8">
|
||||
<strong>Date:</strong> <span t-field="issue.create_date" t-options='{"widget": "date"}'/>
|
||||
</div>
|
||||
<div class='row'>
|
||||
<div class="col-md-6" t-if="issue.user_id">
|
||||
<strong>Assigned to</strong>
|
||||
<div>
|
||||
<address t-field="issue.user_id" t-options='{"widget": "contact", "fields": ["name", "email", "phone"], "no_marker": True}'/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt32">
|
||||
<div class="col-md-6">
|
||||
<strong>Reported by</strong>
|
||||
<div t-if="issue.partner_id">
|
||||
<address t-field="issue.partner_id" t-options='{"widget": "contact", "fields": ["name", "email"], "no_marker": True}'/>
|
||||
</div>
|
||||
</div>
|
||||
<t t-if="issue.description">
|
||||
<div class="col-md-12">
|
||||
<h4><strong>Message and communication history</strong></h4>
|
||||
</div>
|
||||
<div class="col-md-10 col-md-offset-1 mt16">
|
||||
<t t-call="website_mail.message_thread">
|
||||
<t t-set="object" t-value="issue"/>
|
||||
</t>
|
||||
<strong>Description</strong>
|
||||
<div t-field="issue.description"/>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<div t-if="issue.attachment_ids" class='panel-footer'>
|
||||
<strong>
|
||||
<t t-if='len(issue.attachment_ids)>1'>Attachments</t>
|
||||
<t t-else="">Attachment</t>
|
||||
</strong>
|
||||
<div class='row'>
|
||||
<div class="col-md-2 col-sm-3 text-center oe_attachments" t-foreach='issue.attachment_ids' t-as='attachment'>
|
||||
<a t-attf-href="/web/content/#{attachment.id}?download=true" target="_blank">
|
||||
<div class='oe_attachment_embedded o_image o_image_small' t-att-title="attachment.name" t-att-data-mimetype="attachment.mimetype" t-attf-data-src="/web/image/#{attachment.id}/50x40"/>
|
||||
<div class='oe_attachment_name'><t t-esc='attachment.name' /></div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt32">
|
||||
<div class="col-md-12">
|
||||
<h4><strong>Message and communication history</strong></h4>
|
||||
</div>
|
||||
<div class="col-md-10 col-md-offset-1 mt16">
|
||||
<t t-call="website_mail.message_thread">
|
||||
<t t-set="object" t-value="issue"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ class sale_quote(http.Controller):
|
||||
'order_valid': (not order_sudo.validity_date) or (now <= order_sudo.validity_date),
|
||||
'days_valid': days,
|
||||
'action': request.env.ref('sale.action_quotations').id,
|
||||
'breadcrumb': request.env.user.partner_id == order_sudo.partner_id,
|
||||
'no_breadcrumbs': request.env.user.partner_id.commercial_partner_id not in order_sudo.message_partner_ids,
|
||||
'tx_id': Transaction.id if Transaction else False,
|
||||
'tx_state': Transaction.state if Transaction else False,
|
||||
'tx_post_msg': Transaction.acquirer_id.post_msg if Transaction else False,
|
||||
|
||||
@@ -7,6 +7,8 @@ from odoo import api, fields, models, _
|
||||
from odoo.tools.translate import html_translate
|
||||
from odoo.addons import decimal_precision as dp
|
||||
|
||||
from werkzeug.urls import url_encode
|
||||
|
||||
|
||||
class SaleOrderLine(models.Model):
|
||||
_inherit = "sale.order.line"
|
||||
@@ -45,12 +47,6 @@ class SaleOrderLine(models.Model):
|
||||
class SaleOrder(models.Model):
|
||||
_inherit = 'sale.order'
|
||||
|
||||
def _website_url(self):
|
||||
super(SaleOrder, self)._website_url()
|
||||
for so in self:
|
||||
if so.state not in ['sale', 'done']:
|
||||
so.website_url = '/quote/%s' % (so.id)
|
||||
|
||||
template_id = fields.Many2one(
|
||||
'sale.quote.template', 'Quotation Template',
|
||||
readonly=True,
|
||||
@@ -167,11 +163,13 @@ class SaleOrder(models.Model):
|
||||
}
|
||||
|
||||
@api.multi
|
||||
def get_access_action(self):
|
||||
def get_access_action(self, access_uid=None):
|
||||
""" Instead of the classic form view, redirect to the online quote if it exists. """
|
||||
self.ensure_one()
|
||||
if not self.template_id or (not self.env.user.share and not self.env.context.get('force_website')):
|
||||
return super(SaleOrder, self).get_access_action()
|
||||
user = access_uid and self.env['res.users'].sudo().browse(access_uid) or self.env.user
|
||||
|
||||
if not self.template_id or (not user.share and not self.env.context.get('force_website')):
|
||||
return super(SaleOrder, self).get_access_action(access_uid)
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'url': '/quote/%s/%s' % (self.id, self.access_token),
|
||||
@@ -179,6 +177,13 @@ class SaleOrder(models.Model):
|
||||
'res_id': self.id,
|
||||
}
|
||||
|
||||
def get_mail_url(self):
|
||||
self.ensure_one()
|
||||
if self.state not in ['sale', 'done']:
|
||||
auth_param = url_encode(self.partner_id.signup_get_auth_param()[self.partner_id.id])
|
||||
return '/quote/%s/%s?' % (self.id, self.access_token) + auth_param
|
||||
return super(SaleOrder, self).get_mail_url()
|
||||
|
||||
@api.multi
|
||||
def _confirm_online_quote(self, transaction):
|
||||
""" Payment callback: validate the order and write transaction details in chatter """
|
||||
|
||||
@@ -166,20 +166,10 @@
|
||||
|
||||
<!-- Complete page of the quotation -->
|
||||
<template id="so_quotation" name="Product Quotation">
|
||||
<t t-call="website.layout">
|
||||
<t t-call="website_portal.portal_layout">
|
||||
<t t-set="sales_order" t-value="quotation"/> <!-- rename to ensure breadcrumb compatibility with website_portal_sale's portal_layout inheritance -->
|
||||
<body data-spy="scroll" data-target=".navspy" data-offset="50">
|
||||
<div class="container o_website_quote">
|
||||
<div t-if="breadcrumb" class="row">
|
||||
<div class="col-sm-4">
|
||||
<ol class="breadcrumb mt8">
|
||||
<li><a href="/my/home"><i class="fa fa-home"/></a></li>
|
||||
<li><a t-attf-href="/my/quotes?#{keep_query()}">My Quotations</a></li>
|
||||
<li>
|
||||
Quotation <t t-esc="quotation.name"/>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt16">
|
||||
<!-- Sidebar -->
|
||||
<div class="col-md-3 hidden-print">
|
||||
|
||||
@@ -434,7 +434,7 @@ class Slide(models.Model):
|
||||
return fields
|
||||
|
||||
@api.multi
|
||||
def get_access_action(self):
|
||||
def get_access_action(self, access_uid=None):
|
||||
""" Instead of the classic form view, redirect to website if it is published. """
|
||||
self.ensure_one()
|
||||
if self.website_published:
|
||||
@@ -444,7 +444,7 @@ class Slide(models.Model):
|
||||
'target': 'self',
|
||||
'res_id': self.id,
|
||||
}
|
||||
return super(Slide, self).get_access_action()
|
||||
return super(Slide, self).get_access_action(access_uid)
|
||||
|
||||
@api.multi
|
||||
def _notification_recipients(self, message, groups):
|
||||
|
||||
+10
-5
@@ -1339,12 +1339,14 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
|
||||
return False
|
||||
|
||||
@api.multi
|
||||
def get_formview_action(self):
|
||||
def get_formview_action(self, access_uid=None):
|
||||
""" Return an action to open the document ``self``. This method is meant
|
||||
to be overridden in addons that want to give specific view ids for
|
||||
example.
|
||||
"""
|
||||
view_id = self.sudo().get_formview_id(access_uid=self.env.uid)
|
||||
|
||||
An optional access_uid holds the user that will access the document
|
||||
that could be different from the current user. """
|
||||
view_id = self.sudo().get_formview_id(access_uid=access_uid)
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': self._name,
|
||||
@@ -1357,12 +1359,15 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
|
||||
}
|
||||
|
||||
@api.multi
|
||||
def get_access_action(self):
|
||||
def get_access_action(self, access_uid=None):
|
||||
""" Return an action to open the document. This method is meant to be
|
||||
overridden in addons that want to give specific access to the document.
|
||||
By default it opens the formview of the document.
|
||||
|
||||
An optional access_uid holds the user that will access the document
|
||||
that could be different from the current user.
|
||||
"""
|
||||
return self[0].get_formview_action()
|
||||
return self[0].get_formview_action(access_uid=access_uid)
|
||||
|
||||
@api.model
|
||||
def search_count(self, args):
|
||||
|
||||
Reference in New Issue
Block a user