[IMP] sale, website_quote: allow portal payment after signature
Both features already existed but it was not possible to enable both. This makes that possible. When both are enabled the quotation first has to be signed, then it can be paid. Purpose ======= No way to combine digital signature and online payments to confirm orders. A customer signature matters for big deals, in order to keep an official print of customer agreement. Specs ===== - when the customer signs online, a new pdf is generated with the customer signature on it and posted as a note in the chatter, replacing the current signature jpg. - show the signature in the portal view, with both sale_management and website_quote module - change in settings: Digital Signature and Electronic Payment are now 2 checkboxes instead of a radio button - these settings must be company-specific (add company icon if multi-companies, like other company-specific settings) - when both modes are activated, it first request the signature, then the payment - the order is confirmed after payment opw-1818837 Closes #24174
This commit is contained in:
committed by
Romain Derie
parent
803162537b
commit
2fa469c5e5
@@ -200,16 +200,11 @@ class CustomerPortal(CustomerPortal):
|
||||
]
|
||||
return request.make_response(pdf, headers=pdfhttpheaders)
|
||||
|
||||
def _portal_quote_user_can_accept(self, order_id):
|
||||
return request.env['ir.config_parameter'].sudo().get_param('sale.sale_portal_confirmation_options', default='none') in ('pay', 'sign')
|
||||
def _portal_quote_user_can_accept(self, order):
|
||||
return order.company_id.portal_confirmation_sign or order.company_id.portal_confirmation_pay
|
||||
|
||||
@http.route(['/my/quotes/accept'], type='json', auth="public", website=True)
|
||||
def portal_quote_accept(self, res_id, access_token=None, partner_name=None, signature=None):
|
||||
if not self._portal_quote_user_can_accept(res_id):
|
||||
return {'error': _('Operation not allowed')}
|
||||
if not signature:
|
||||
return {'error': _('Signature is missing.')}
|
||||
|
||||
try:
|
||||
order_sudo = self._order_check_access(res_id, access_token=access_token)
|
||||
except AccessError:
|
||||
@@ -217,15 +212,29 @@ class CustomerPortal(CustomerPortal):
|
||||
if order_sudo.state != 'sent':
|
||||
return {'error': _('Order is not in a state requiring customer validation.')}
|
||||
|
||||
order_sudo.action_confirm()
|
||||
if not self._portal_quote_user_can_accept(order_sudo):
|
||||
return {'error': _('Operation not allowed')}
|
||||
if not signature:
|
||||
return {'error': _('Signature is missing.')}
|
||||
|
||||
success_message = _('Your order has been signed but still needs to be paid to be confirmed.')
|
||||
|
||||
if not order_sudo.has_to_be_paid():
|
||||
order_sudo.action_confirm()
|
||||
success_message = _('Your order has been confirmed.')
|
||||
|
||||
order_sudo.signature = signature
|
||||
order_sudo.signed_by = partner_name
|
||||
|
||||
pdf = request.env.ref('sale.action_report_saleorder').sudo().render_qweb_pdf([order_sudo.id])[0]
|
||||
_message_post_helper(
|
||||
res_model='sale.order',
|
||||
res_id=order_sudo.id,
|
||||
message=_('Order signed by %s') % (partner_name,),
|
||||
attachments=[('signature.png', base64.b64decode(signature))] if signature else [],
|
||||
attachments=[('%s.pdf' % order_sudo.name, pdf)],
|
||||
**({'token': access_token} if access_token else {}))
|
||||
|
||||
return {
|
||||
'success': _('Your Order has been confirmed.'),
|
||||
'success': success_message,
|
||||
'redirect_url': '/my/orders/%s?%s' % (order_sudo.id, access_token and 'access_token=%s' % order_sudo.access_token or ''),
|
||||
}
|
||||
|
||||
@@ -69,8 +69,16 @@
|
||||
% set is_online = access_action and access_action['type'] == 'ir.actions.act_url'
|
||||
% set doc_name = 'quotation' if object.state in ('draft', 'sent') else 'order confirmation'
|
||||
% set pay_sign_name = object.get_portal_confirmation_action()
|
||||
% set access_name = is_online and object.state in ('draft', 'sent') and pay_sign_name in ('pay', 'sign') and 'Accept and %s online' % pay_sign_name or 'View %s' % doc_name
|
||||
% set access_url = is_online and object.get_mail_url() or ''
|
||||
% set access_name = 'View %s' % doc_name
|
||||
|
||||
% if is_online and object.state in ('draft', 'sent') and pay_sign_name in ('pay', 'sign'):
|
||||
% if pay_sign_name == 'sign' and object.has_to_be_paid():
|
||||
% set access_name = 'Sign and pay online'
|
||||
% else:
|
||||
% set access_name = 'Accept and %s online' % pay_sign_name
|
||||
% endif
|
||||
% endif
|
||||
|
||||
% if object.partner_id.parent_id:
|
||||
(<i>${object.partner_id.parent_id.name}</i>)
|
||||
|
||||
@@ -8,3 +8,5 @@ class ResCompany(models.Model):
|
||||
_inherit = "res.company"
|
||||
|
||||
sale_note = fields.Text(string='Default Terms and Conditions', translate=True)
|
||||
portal_confirmation_sign = fields.Boolean(string='Digital Signature', default=True)
|
||||
portal_confirmation_pay = fields.Boolean(string='Electronic Payment')
|
||||
|
||||
@@ -18,12 +18,9 @@ class ResConfigSettings(models.TransientModel):
|
||||
module_sale_margin = fields.Boolean("Margins")
|
||||
group_sale_layout = fields.Boolean("Sections on Sales Orders", implied_group='sale.group_sale_layout')
|
||||
group_warning_sale = fields.Boolean("Sale Order Warnings", implied_group='sale.group_warning_sale')
|
||||
portal_confirmation = fields.Boolean('Online Signature & Payment')
|
||||
portal_confirmation_options = fields.Selection([
|
||||
('sign', 'Signature'),
|
||||
('pay', 'Payment')], string="Online Signature & Payment options",
|
||||
config_parameter='sale.sale_portal_confirmation_options')
|
||||
module_sale_payment = fields.Boolean("Sale Payment", help='Technical field implied by user choice of online_confirmation')
|
||||
portal_confirmation_sign = fields.Boolean(related='company_id.portal_confirmation_sign', string='Digital Signature')
|
||||
portal_confirmation_pay = fields.Boolean(related='company_id.portal_confirmation_pay', string='Electronic Payment')
|
||||
module_sale_payment = fields.Boolean("Sale Payment", help='Technical field implied by user choice of portal_confirmation_pay.')
|
||||
module_website_quote = fields.Boolean("Quotations Templates")
|
||||
group_sale_delivery_address = fields.Boolean("Customer Addresses", implied_group='sale.group_delivery_invoice_address')
|
||||
multi_sales_price = fields.Boolean("Multiple Sales Prices per Product")
|
||||
@@ -131,16 +128,9 @@ class ResConfigSettings(models.TransientModel):
|
||||
'group_pricelist_item': False,
|
||||
})
|
||||
|
||||
@api.onchange('portal_confirmation')
|
||||
def _onchange_portal_confirmation(self):
|
||||
if not self.portal_confirmation:
|
||||
self.portal_confirmation_options = False
|
||||
elif not self.portal_confirmation_options:
|
||||
self.portal_confirmation_options = 'sign'
|
||||
|
||||
@api.onchange('portal_confirmation_options')
|
||||
def _onchange_portal_confirmation_options(self):
|
||||
if self.portal_confirmation_options == 'pay':
|
||||
@api.onchange('portal_confirmation_pay')
|
||||
def _onchange_portal_confirmation_pay(self):
|
||||
if self.portal_confirmation_pay:
|
||||
self.module_sale_payment = True
|
||||
|
||||
@api.model
|
||||
@@ -152,6 +142,5 @@ class ResConfigSettings(models.TransientModel):
|
||||
multi_sales_price=sale_pricelist_setting in ['percentage', 'formula'],
|
||||
multi_sales_price_method=sale_pricelist_setting in ['percentage', 'formula'] and sale_pricelist_setting or False,
|
||||
sale_pricelist_setting=sale_pricelist_setting,
|
||||
portal_confirmation=ICPSudo.get_param('sale.sale_portal_confirmation_options', default=False) in ('pay', 'sign'),
|
||||
)
|
||||
return res
|
||||
|
||||
@@ -163,6 +163,9 @@ class SaleOrder(models.Model):
|
||||
company_id = fields.Many2one('res.company', 'Company', default=lambda self: self.env['res.company']._company_default_get('sale.order'))
|
||||
team_id = fields.Many2one('crm.team', 'Sales Channel', change_default=True, default=_get_default_team, oldname='section_id')
|
||||
|
||||
signature = fields.Binary('Signature', help='Signature received through the portal.', copy=False, attachment=True)
|
||||
signed_by = fields.Char('Signed by', help='Name of the person that signed the SO.', copy=False)
|
||||
|
||||
product_id = fields.Many2one('product.product', related='order_line.product_id', string='Product')
|
||||
|
||||
def _compute_portal_url(self):
|
||||
@@ -488,6 +491,8 @@ class SaleOrder(models.Model):
|
||||
orders = self.filtered(lambda s: s.state in ['cancel', 'sent'])
|
||||
return orders.write({
|
||||
'state': 'draft',
|
||||
'signature': False,
|
||||
'signed_by': False,
|
||||
})
|
||||
|
||||
@api.multi
|
||||
@@ -673,7 +678,18 @@ class SaleOrder(models.Model):
|
||||
return self.get_share_url()
|
||||
|
||||
def get_portal_confirmation_action(self):
|
||||
return self.env['ir.config_parameter'].sudo().get_param('sale.sale_portal_confirmation_options', default='none')
|
||||
if self.company_id.portal_confirmation_sign and not self.signature:
|
||||
return 'sign'
|
||||
if self.company_id.portal_confirmation_pay:
|
||||
return 'pay'
|
||||
|
||||
return 'none'
|
||||
|
||||
def has_to_be_signed(self):
|
||||
return self.company_id.portal_confirmation_sign
|
||||
|
||||
def has_to_be_paid(self):
|
||||
return self.company_id.portal_confirmation_pay
|
||||
|
||||
@api.multi
|
||||
def _notify_get_groups(self, message, groups):
|
||||
|
||||
@@ -202,6 +202,19 @@
|
||||
<strong>Fiscal Position Remark:</strong>
|
||||
<span t-field="doc.fiscal_position_id.note"/>
|
||||
</p>
|
||||
|
||||
<div t-if="doc.signature" class="row mt32" name="signature">
|
||||
<div class="col-xs-offset-8 col-xs-4">
|
||||
<strong>Signature</strong>
|
||||
</div>
|
||||
<div class="col-xs-offset-8 col-xs-4">
|
||||
<img t-att-src="'data:image/png;base64,%s' % to_text(doc.signature)" style="max-height: 4cm; max-width: 8cm;"/>
|
||||
</div>
|
||||
<div class="col-xs-offset-8 col-xs-4 text-center">
|
||||
<p t-field="doc.signed_by"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="oe_structure"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
@@ -139,6 +139,31 @@
|
||||
</div>
|
||||
<h2>Quotations & Orders</h2>
|
||||
<div class="row mt16 o_settings_container">
|
||||
<div class="col-xs-12 col-md-6 o_setting_box" id="sale_config_online_confirmation_sign">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="portal_confirmation_sign"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="portal_confirmation_sign"/>
|
||||
<span class="fa fa-lg fa-building-o" title="Values set here are company-specific." groups="base.group_multi_company"/>
|
||||
<div class="text-muted">
|
||||
Request a digital signature to confirm orders
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md-6 o_setting_box" id="sale_config_online_confirmation_pay">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="portal_confirmation_pay"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<field name="module_sale_payment" invisible="1"/>
|
||||
<label for="portal_confirmation_pay"/>
|
||||
<span class="fa fa-lg fa-building-o" title="Values set here are company-specific." groups="base.group_multi_company"/>
|
||||
<div class="text-muted">
|
||||
Request an electronic payment to confirm orders
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md-6 o_setting_box">
|
||||
<div class="o_setting_left_pane">
|
||||
</div>
|
||||
@@ -154,24 +179,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md-6 o_setting_box" id="sale_config_online_confirmation">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="portal_confirmation"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<field name="module_sale_payment" invisible="1"/>
|
||||
<label for="portal_confirmation"/>
|
||||
<div class="text-muted">
|
||||
Let your customers sign & pay online
|
||||
</div>
|
||||
<div class="content-group" attrs="{'invisible': [('portal_confirmation', '=', False)]}">
|
||||
<div class="mt16">
|
||||
<field name="portal_confirmation_options" class="o_light_label" widget="radio"
|
||||
attrs="{'required': [('portal_confirmation', '=', True)]}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md-6 o_setting_box">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="module_website_quote"/>
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
<field name="model">res.config.settings</field>
|
||||
<field name="inherit_id" ref="sale.res_config_settings_view_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='portal_confirmation_options']/.." position="after">
|
||||
<div attrs="{'invisible': [('portal_confirmation_options', '!=', 'pay')]}">
|
||||
<xpath expr="//label[@for='portal_confirmation_pay']/.." position="inside">
|
||||
<div attrs="{'invisible': [('portal_confirmation_pay', '=', False)]}">
|
||||
<button name='%(payment.action_payment_acquirer)d' icon="fa-arrow-right" type="action" string="Payment Acquirers" class="btn-link"/>
|
||||
</div>
|
||||
</xpath>
|
||||
|
||||
@@ -24,11 +24,10 @@ class CustomerPortal(CustomerPortal):
|
||||
return request.redirect('/quote/%s/%s' % (order, access_token or ''))
|
||||
return super(CustomerPortal, self).portal_order_page(order=order, access_token=access_token, **kw)
|
||||
|
||||
def _portal_quote_user_can_accept(self, order_id):
|
||||
result = super(CustomerPortal, self)._portal_quote_user_can_accept(order_id)
|
||||
order_sudo = request.env['sale.order'].sudo().browse(order_id)
|
||||
def _portal_quote_user_can_accept(self, order):
|
||||
result = super(CustomerPortal, self)._portal_quote_user_can_accept(order)
|
||||
# either use quote template settings or fallback on default behavior
|
||||
return not order_sudo.require_payment if order_sudo.template_id else result
|
||||
return order.require_signature if order.template_id else result
|
||||
|
||||
|
||||
class sale_quote(http.Controller):
|
||||
@@ -85,7 +84,7 @@ class sale_quote(http.Controller):
|
||||
'partner_id': order_sudo.partner_id.id,
|
||||
}
|
||||
|
||||
if order_sudo.require_payment or values['need_payment']:
|
||||
if order_sudo.has_to_be_paid() or values['need_payment']:
|
||||
domain = expression.AND([
|
||||
['&', ('website_published', '=', True), ('company_id', '=', order_sudo.company_id.id)],
|
||||
['|', ('specific_countries', '=', False), ('country_ids', 'in', [order_sudo.partner_id.country_id.id])]
|
||||
|
||||
@@ -6,6 +6,7 @@ from datetime import datetime, timedelta
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.tools.translate import html_translate
|
||||
from odoo.addons import decimal_precision as dp
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
from werkzeug.urls import url_encode
|
||||
|
||||
@@ -51,16 +52,19 @@ class SaleOrder(models.Model):
|
||||
template = self.env.ref('website_quote.website_quote_template_default', raise_if_not_found=False)
|
||||
return template and template.active and template or False
|
||||
|
||||
def _get_default_online_payment(self):
|
||||
def _get_default_require_signature(self):
|
||||
default_template = self._get_default_template()
|
||||
if self.template_id:
|
||||
return self.template_id.require_payment
|
||||
elif default_template:
|
||||
return default_template.require_payment
|
||||
elif self.env['ir.config_parameter'].sudo().get_param('sale.sale_portal_confirmation_options', default='none') == 'pay':
|
||||
return 1
|
||||
if default_template:
|
||||
return default_template.require_signature
|
||||
else:
|
||||
return 0
|
||||
return False
|
||||
|
||||
def _get_default_require_payment(self):
|
||||
default_template = self._get_default_template()
|
||||
if default_template:
|
||||
return default_template.require_payment
|
||||
else:
|
||||
return False
|
||||
|
||||
template_id = fields.Many2one(
|
||||
'sale.quote.template', 'Quotation Template',
|
||||
@@ -75,12 +79,12 @@ class SaleOrder(models.Model):
|
||||
amount_undiscounted = fields.Float(
|
||||
'Amount Before Discount', compute='_compute_amount_undiscounted', digits=0)
|
||||
quote_viewed = fields.Boolean('Quotation Viewed')
|
||||
require_payment = fields.Selection([
|
||||
(0, 'Online Signature'),
|
||||
(1, 'Online Payment')], default=_get_default_online_payment, string='Confirmation Mode',
|
||||
help="Choose how you want to confirm an order to launch the delivery process. You can either "
|
||||
"request a digital signature or an upfront payment. With a digital signature, you can "
|
||||
"request the payment when issuing the invoice.")
|
||||
require_signature = fields.Boolean('Digital Signature', default=_get_default_require_signature,
|
||||
states={'sale': [('readonly', True)], 'done': [('readonly', True)]},
|
||||
help='Request a digital signature to the customer in order to confirm orders automatically.')
|
||||
require_payment = fields.Boolean('Electronic Payment', default=_get_default_require_payment,
|
||||
states={'sale': [('readonly', True)], 'done': [('readonly', True)]},
|
||||
help='Request an electronic payment to the customer in order to confirm orders automatically.')
|
||||
|
||||
@api.multi
|
||||
@api.returns('self', lambda value: value.id)
|
||||
@@ -113,6 +117,8 @@ class SaleOrder(models.Model):
|
||||
@api.onchange('template_id')
|
||||
def onchange_template_id(self):
|
||||
if not self.template_id:
|
||||
self.require_signature = False
|
||||
self.require_payment = False
|
||||
return
|
||||
template = self.template_id.with_context(lang=self.partner_id.lang)
|
||||
|
||||
@@ -170,11 +176,18 @@ class SaleOrder(models.Model):
|
||||
self.validity_date = fields.Date.to_string(datetime.now() + timedelta(template.number_of_days))
|
||||
|
||||
self.website_description = template.website_description
|
||||
self.require_signature = template.require_signature
|
||||
self.require_payment = template.require_payment
|
||||
|
||||
if template.note:
|
||||
self.note = template.note
|
||||
|
||||
@api.constrains('template_id', 'require_signature', 'require_payment')
|
||||
def _check_portal_confirmation(self):
|
||||
for order in self.sudo().filtered('template_id'):
|
||||
if not order.require_signature and not order.require_payment:
|
||||
raise ValidationError(_('Please select a confirmation mode in Other Information: Digital Signature, Electronic Payment or both.'))
|
||||
|
||||
@api.multi
|
||||
def open_quotation(self):
|
||||
self.ensure_one()
|
||||
@@ -209,10 +222,23 @@ class SaleOrder(models.Model):
|
||||
|
||||
def get_portal_confirmation_action(self):
|
||||
""" Template override default behavior of pay / sign chosen in sales settings """
|
||||
if self.require_payment is not None or self.require_payment is not False:
|
||||
return 'pay' if self.require_payment == 1 else 'sign'
|
||||
if self.template_id:
|
||||
if self.require_signature and not self.signature:
|
||||
return 'sign'
|
||||
elif self.require_payment:
|
||||
return 'pay'
|
||||
else:
|
||||
return 'none'
|
||||
return super(SaleOrder, self).get_portal_confirmation_action()
|
||||
|
||||
def has_to_be_signed(self):
|
||||
res = super(SaleOrder, self).has_to_be_signed()
|
||||
return self.require_signature if self.template_id else res
|
||||
|
||||
def has_to_be_paid(self):
|
||||
res = super(SaleOrder, self).has_to_be_paid()
|
||||
return self.require_payment if self.template_id else res
|
||||
|
||||
@api.multi
|
||||
def action_confirm(self):
|
||||
res = super(SaleOrder, self).action_confirm()
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.tools.translate import html_translate
|
||||
from odoo.addons import decimal_precision as dp
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class SaleQuoteTemplate(models.Model):
|
||||
@@ -17,18 +18,20 @@ class SaleQuoteTemplate(models.Model):
|
||||
options = fields.One2many('sale.quote.option', 'template_id', 'Optional Products Lines', copy=True)
|
||||
number_of_days = fields.Integer('Quotation Duration',
|
||||
help='Number of days for the validity date computation of the quotation')
|
||||
require_payment = fields.Selection([
|
||||
(0, 'Online Signature'),
|
||||
(1, 'Online Payment')], default=0, string='Confirmation Mode',
|
||||
help="Choose how you want to confirm an order to launch the delivery process. You can either "
|
||||
"request a digital signature or an upfront payment. With a digital signature, you can "
|
||||
"request the payment when issuing the invoice.")
|
||||
require_signature = fields.Boolean('Digital Signature', default=True, help='Request a digital signature to the customer in order to confirm orders automatically.')
|
||||
require_payment = fields.Boolean('Electronic Payment', help='Request an electronic payment to the customer in order to confirm orders automatically.')
|
||||
mail_template_id = fields.Many2one(
|
||||
'mail.template', 'Confirmation Mail',
|
||||
domain=[('model', '=', 'sale.order')],
|
||||
help="This e-mail template will be sent on confirmation. Leave empty to send nothing.")
|
||||
active = fields.Boolean(default=True, help="If unchecked, it will allow you to hide the quotation template without removing it.")
|
||||
|
||||
|
||||
@api.constrains('require_signature', 'require_payment')
|
||||
def _check_confirmation(self):
|
||||
for template in self:
|
||||
if not template.require_signature and not template.require_payment:
|
||||
raise ValidationError(_('Please select a confirmation mode in Confirmation: Digital Signature, Electronic Payment or both.'))
|
||||
|
||||
@api.multi
|
||||
def open_template(self):
|
||||
self.ensure_one()
|
||||
|
||||
@@ -37,7 +37,8 @@
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='client_order_ref']" position="after">
|
||||
<field name="require_payment" widget="radio"/>
|
||||
<field name="require_signature" attrs="{'invisible': [('template_id', '=', False)]}"/>
|
||||
<field name="require_payment" attrs="{'invisible': [('template_id', '=', False)]}"/>
|
||||
<field name="website_description" invisible="1"/>
|
||||
</xpath>
|
||||
</field>
|
||||
|
||||
@@ -94,7 +94,8 @@
|
||||
</page>
|
||||
<page string="Confirmation">
|
||||
<group>
|
||||
<field name="require_payment" widget="radio"/>
|
||||
<field name="require_signature"/>
|
||||
<field name="require_payment"/>
|
||||
<field name="mail_template_id"/>
|
||||
</group>
|
||||
</page>
|
||||
|
||||
@@ -145,6 +145,20 @@
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<template id="signature" name="Signature">
|
||||
<div t-if="quotation.signature" class="row mt64" name="signature">
|
||||
<div class="col-xs-offset-8 col-xs-4">
|
||||
<strong>Signature</strong>
|
||||
</div>
|
||||
<div class="col-xs-offset-8 col-xs-4">
|
||||
<img t-att-src="'data:image/png;base64,%s' % to_text(quotation.signature)" style="max-height: 4cm; max-width: 8cm;"/>
|
||||
</div>
|
||||
<div class="col-xs-offset-8 col-xs-4 text-center">
|
||||
<p t-field="quotation.signed_by"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="change_quantity" inherit_id="website_quote.pricing" active="False" customize_show="True" name="Change Quantity">
|
||||
<xpath expr="//div[@id='quote_qty']" position="replace">
|
||||
<div class="input-group oe_website_spinner">
|
||||
@@ -202,11 +216,11 @@
|
||||
<div class="col-md-3 hidden-print">
|
||||
<div class="bs-sidebar">
|
||||
<div class="text-center" t-if="quotation.state in ('draft', 'sent', 'waiting_date')" style="padding: 10px">
|
||||
<a role="button" t-if="order_valid and not quotation.require_payment" class="btn btn-primary btn-block" data-toggle="modal" data-target="#modalaccept">
|
||||
<a role="button" t-if="order_valid and not quotation.signature and quotation.has_to_be_signed()" class="btn btn-primary btn-block" data-toggle="modal" data-target="#modalaccept">
|
||||
<span class="fa fa-check"> Accept Order</span>
|
||||
</a>
|
||||
<a role="button" t-if="order_valid and quotation.require_payment" class="btn btn-primary btn-block" data-toggle="modal" data-target="#modalaccept">
|
||||
<span class="fa fa-check"> Accept & Pay</span>
|
||||
<a role="button" t-elif="order_valid and quotation.has_to_be_paid()" class="btn btn-primary btn-block" data-toggle="modal" data-target="#modalaccept">
|
||||
<span class="fa fa-check"> <t t-if="not quotation.signature">Accept & </t>Pay</span>
|
||||
</a>
|
||||
<a role="button" t-if="not order_valid" href="#discussion" class="btn btn-info btn-block o_normalwhitespace">
|
||||
<strong>This offer expired!</strong><br/>
|
||||
@@ -218,7 +232,7 @@
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center" t-if="need_payment or (quotation.state in ('sale') and quotation.require_payment and tx_state != 'done')" style="padding: 10px">
|
||||
<div class="text-center" t-if="need_payment or (quotation.state in ('sale') and quotation.has_to_be_paid() and tx_state != 'done')" style="padding: 10px">
|
||||
<a role="button" class="btn btn-primary btn-block" data-toggle="modal" data-target="#modalaccept">
|
||||
<span class="fa fa-check"> Pay Now</span>
|
||||
</a>
|
||||
@@ -301,7 +315,7 @@
|
||||
|
||||
<!-- modal relative to the actions Accept/Reject/Cancel -->
|
||||
<div role="dialog" class="modal fade" id="modalaccept">
|
||||
<div class="modal-dialog" t-if="not quotation.require_payment and not need_payment">
|
||||
<div class="modal-dialog" t-if="not quotation.signature and quotation.has_to_be_signed()">
|
||||
<form id="accept" method="POST" t-att-data-order-id="quotation.id" t-att-data-token="quotation.access_token" class="js_accept_json modal-content js_website_submit_form">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
<header class="modal-header">
|
||||
@@ -326,7 +340,7 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="modal-dialog modal-content" t-if="quotation.require_payment or need_payment">
|
||||
<div class="modal-dialog modal-content" t-if="not (quotation.has_to_be_signed() and not quotation.signature) and (quotation.has_to_be_paid() or need_payment)">
|
||||
<header class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">&times;</button>
|
||||
<h4 class="modal-title">Validate Order</h4>
|
||||
@@ -386,17 +400,17 @@
|
||||
<t t-call="website_quote.so_quotation_content"/>
|
||||
|
||||
<div class="text-center hidden-print mt16 mb16" id="quotation_toolbar" t-if="quotation.state in ('draft', 'sent', 'waiting_date')">
|
||||
<a role="button" class="btn btn-success" data-toggle="modal" data-target="#modalaccept" t-if="order_valid and not quotation.require_payment">
|
||||
<a role="button" class="btn btn-success" data-toggle="modal" data-target="#modalaccept" t-if="order_valid and not quotation.has_to_be_paid()">
|
||||
<span class="fa fa-check"> Accept</span>
|
||||
</a>
|
||||
<a role="button" class="btn btn-success" data-toggle="modal" data-target="#modalaccept" t-if="order_valid and quotation.require_payment">
|
||||
<a role="button" class="btn btn-success" data-toggle="modal" data-target="#modalaccept" t-if="order_valid and quotation.has_to_be_paid()">
|
||||
<span class="fa fa-check"> Accept & Pay</span>
|
||||
</a>
|
||||
<a role="button" class="btn btn-danger" data-toggle="modal" data-target="#modaldecline">
|
||||
<span class="fa fa-times"> Reject</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="text-center" t-if="need_payment or (quotation.state in ('sale') and quotation.require_payment and tx_state != 'done')" style="padding: 10px">
|
||||
<div class="text-center" t-if="need_payment or (quotation.state in ('sale') and quotation.has_to_be_paid() and tx_state != 'done')" style="padding: 10px">
|
||||
<a role="button" class="btn btn-primary" data-toggle="modal" data-target="#modalaccept">
|
||||
<span class="fa fa-check"> Pay Now</span>
|
||||
</a>
|
||||
@@ -535,6 +549,8 @@
|
||||
<div class="oe_structure"/>
|
||||
<!-- Pricing -->
|
||||
<t t-call="website_quote.pricing"/>
|
||||
<!-- Signature -->
|
||||
<t t-call="website_quote.signature"/>
|
||||
<!-- Options -->
|
||||
<t t-call="website_quote.optional_products"/>
|
||||
</template>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
Show the automatic invoices setting in sales settings.
|
||||
-->
|
||||
<div class="col-xs-12 col-md-6 o_setting_box"
|
||||
attrs="{'invisible': ['|', ('default_invoice_policy', '!=', 'order'), '&', ('portal_confirmation_options', '!=', 'pay'), ('module_website_quote', '=', False)]}">
|
||||
attrs="{'invisible': ['|', ('default_invoice_policy', '!=', 'order'), '&', ('portal_confirmation_pay', '=', False), ('module_website_quote', '=', False)]}">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="automatic_invoice" nolabel="1"/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user