[IMP] various,sale: improve sign, pay and reject
Flow ==== Have one model field responsible of telling when the sales order has to be signed or paid to avoid inconsistencies. The general rule is: sign & pay are only used upfront to confirm a sales order. If a sales person starts to modify (confirm) it manually, we don't need those features. Especially at that point pay should be done with invoices. Modal ===== For the sign, we want to force refresh the page to update its state, and thus we need to show the confirmation message on the reloaded page. For sign and pay, we changed a bit the structure of the text in the modal to allow to translate it more easily. Also fixed the payment selection by moving it into the body instead of the footer. For the reject modal, we want the feedback field to be required, so we can see in the chatter by who and why it has been rejected. Misc ==== Moved remaining bits of code related to preview/pay/accept/decline from sale_management into sale, where it should have been in the first place. Sales Order: - Improved status/contextual alerts. - Hide discount from small to fix responsive. PR: #26801 task-1876864 remove sales help
This commit is contained in:
committed by
Romain Derie
parent
0a52493444
commit
34c47eecd3
@@ -102,7 +102,7 @@
|
||||
<a class="btn btn-secondary btn-block o_download_btn" t-att-href="invoice.get_portal_url(report_type='pdf', download=True)" title="Download"><i class="fa fa-download"/> Download</a>
|
||||
</div>
|
||||
<div class="btn-group flex-grow-1 mb-1">
|
||||
<a class="btn btn-secondary btn-block o_print_btn o_portal_invoice_print" t-att-href="invoice.get_portal_url(report_type='pdf')" id="print_invoice_report" title="Print"><i class="fa fa-print"/> Print</a>
|
||||
<a class="btn btn-secondary btn-block o_print_btn o_portal_invoice_print" t-att-href="invoice.get_portal_url(report_type='pdf')" id="print_invoice_report" title="Print" target="_blank"><i class="fa fa-print"/> Print</a>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
<h3 class="modal-title">Pay with</h3>
|
||||
<button type="button" class="close" data-dismiss="modal">&times;</button>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="modal-body">
|
||||
<div t-if="pms or s2s_acquirers or form_acquirers" id="payment_method" class="text-left col-md-13">
|
||||
<t t-call="payment.payment_tokens_list">
|
||||
<t t-set="mode" t-value="'payment'"/>
|
||||
@@ -55,7 +55,7 @@
|
||||
<t t-set="callback_method" t-value="''"/>
|
||||
<t t-set="form_action" t-value="'/invoice/pay/' + str(invoice.id) + '/s2s_token_tx/'"/>
|
||||
<t t-set="prepare_tx_url" t-value="'/invoice/pay/' + str(invoice.id) + '/form_tx/'"/>
|
||||
<t t-set="submit_txt" t-value="'Pay Now'"/>
|
||||
<t t-set="submit_txt">Pay Now</t>
|
||||
<t t-set="icon_class" t-value="'fa-lock'"/>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
@@ -229,7 +229,6 @@
|
||||
<t t-set="access_action" t-value="record.with_context(force_website=True).get_access_action()"/>
|
||||
<t t-set="is_online" t-value="access_action and access_action['type'] == 'ir.actions.act_url'"/>
|
||||
<t t-set="access_url" t-value="is_online and record._get_share_url(redirect=True, signup_partner=True) or ''"/>
|
||||
<t t-set="access_code" t-value="'view'"/>
|
||||
<t t-set="access_name">
|
||||
View <t t-esc="record._description.lower()"/>
|
||||
</t>
|
||||
|
||||
@@ -91,6 +91,13 @@ odoo.define('portal.signature_form', function (require){
|
||||
var $success = qweb.render("portal.portal_signature_success", {widget: data});
|
||||
self.$('#o_portal_sign_draw').parent().replaceWith($success);
|
||||
}
|
||||
if (data.force_refresh) {
|
||||
if (data.redirect_url) {
|
||||
window.location = data.redirect_url;
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@@ -158,13 +158,9 @@ class CustomerPortal(CustomerPortal):
|
||||
body = _('Quotation viewed by customer')
|
||||
_message_post_helper(res_model='sale.order', res_id=order_sudo.id, message=body, token=order_sudo.access_token, message_type='notification', subtype="mail.mt_note", partner_ids=order_sudo.user_id.sudo().partner_id.ids)
|
||||
|
||||
transaction = order_sudo.get_portal_last_transaction()
|
||||
|
||||
values = {
|
||||
'sale_order': order_sudo,
|
||||
'message': int(message) if message else False,
|
||||
'tx_state': transaction.state if transaction else False,
|
||||
'need_payment': order_sudo.invoice_status == 'to invoice' and transaction.state in ['draft', 'cancel'],
|
||||
'message': message,
|
||||
'token': access_token,
|
||||
'return_url': '/shop/payment/validate',
|
||||
'bootstrap_formatting': True,
|
||||
@@ -172,7 +168,7 @@ class CustomerPortal(CustomerPortal):
|
||||
'report_type': 'html',
|
||||
}
|
||||
|
||||
if order_sudo.has_to_be_paid() or values['need_payment']:
|
||||
if order_sudo.has_to_be_paid():
|
||||
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])]
|
||||
@@ -192,3 +188,119 @@ class CustomerPortal(CustomerPortal):
|
||||
values.update(get_records_pager(history, order_sudo))
|
||||
|
||||
return request.render('sale.sale_order_portal_template', values)
|
||||
|
||||
@http.route(['/my/orders/<int:order_id>/accept'], type='json', auth="public", website=True)
|
||||
def portal_quote_accept(self, res_id, access_token=None, partner_name=None, signature=None, order_id=None):
|
||||
try:
|
||||
order_sudo = self._document_check_access('sale.order', res_id, access_token=access_token)
|
||||
except (AccessError, MissingError):
|
||||
return {'error': _('Invalid order')}
|
||||
|
||||
if not order_sudo.has_to_be_signed():
|
||||
return {'error': _('Order is not in a state requiring customer signature.')}
|
||||
if not signature:
|
||||
return {'error': _('Signature is missing.')}
|
||||
|
||||
if not order_sudo.has_to_be_paid():
|
||||
order_sudo.action_confirm()
|
||||
|
||||
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=[('%s.pdf' % order_sudo.name, pdf)],
|
||||
**({'token': access_token} if access_token else {}))
|
||||
|
||||
return {
|
||||
'force_refresh': True,
|
||||
'redirect_url': order_sudo.get_portal_url(query_string='&message=sign_ok'),
|
||||
}
|
||||
|
||||
@http.route(['/my/orders/<int:order_id>/decline'], type='http', auth="public", methods=['POST'], website=True)
|
||||
def decline(self, order_id, access_token=None, **post):
|
||||
try:
|
||||
order_sudo = self._document_check_access('sale.order', order_id, access_token=access_token)
|
||||
except (AccessError, MissingError):
|
||||
return request.redirect('/my')
|
||||
|
||||
message = post.get('decline_message')
|
||||
|
||||
query_string = False
|
||||
if order_sudo.has_to_be_signed() and message:
|
||||
order_sudo.action_cancel()
|
||||
_message_post_helper(message=message, res_id=order_id, res_model='sale.order', **{'token': access_token} if access_token else {})
|
||||
else:
|
||||
query_string = "&message=cant_reject"
|
||||
|
||||
return request.redirect(order_sudo.get_portal_url(query_string=query_string))
|
||||
|
||||
# note dbo: website_sale code
|
||||
@http.route(['/my/orders/<int:order_id>/transaction/'], type='json', auth="public", website=True)
|
||||
def payment_transaction_token(self, acquirer_id, order_id, save_token=False, access_token=None, **kwargs):
|
||||
""" Json method that creates a payment.transaction, used to create a
|
||||
transaction when the user clicks on 'pay now' button. After having
|
||||
created the transaction, the event continues and the user is redirected
|
||||
to the acquirer website.
|
||||
|
||||
:param int acquirer_id: id of a payment.acquirer record. If not set the
|
||||
user is redirected to the checkout page
|
||||
"""
|
||||
# Ensure a payment acquirer is selected
|
||||
if not acquirer_id:
|
||||
return False
|
||||
|
||||
try:
|
||||
acquirer_id = int(acquirer_id)
|
||||
except:
|
||||
return False
|
||||
|
||||
order = request.env['sale.order'].sudo().browse(order_id)
|
||||
if not order or not order.order_line or not order.has_to_be_paid():
|
||||
return False
|
||||
|
||||
# Create transaction
|
||||
vals = {
|
||||
'acquirer_id': acquirer_id,
|
||||
'type': order._get_payment_type(),
|
||||
}
|
||||
|
||||
transaction = order._create_payment_transaction(vals)
|
||||
|
||||
return transaction.render_sale_button(
|
||||
order,
|
||||
order.get_portal_url(),
|
||||
submit_txt=_('Pay & Confirm'),
|
||||
render_values={
|
||||
'type': order._get_payment_type(),
|
||||
'alias_usage': _('If we store your payment information on our server, subscription payments will be made automatically.'),
|
||||
}
|
||||
)
|
||||
|
||||
@http.route('/my/orders/<int:order_id>/transaction/token', type='http', auth='public', website=True)
|
||||
def payment_token(self, order_id, pm_id=None, **kwargs):
|
||||
|
||||
order = request.env['sale.order'].sudo().browse(order_id)
|
||||
if not order:
|
||||
return request.redirect("/my/orders")
|
||||
if not order.order_line or pm_id is None or not order.has_to_be_paid():
|
||||
return request.redirect(order.get_portal_url())
|
||||
|
||||
# try to convert pm_id into an integer, if it doesn't work redirect the user to the quote
|
||||
try:
|
||||
pm_id = int(pm_id)
|
||||
except ValueError:
|
||||
return request.redirect(order.get_portal_url())
|
||||
|
||||
# Create transaction
|
||||
vals = {
|
||||
'payment_token_id': pm_id,
|
||||
'type': 'server2server',
|
||||
}
|
||||
|
||||
order._create_payment_transaction(vals)
|
||||
|
||||
return request.redirect(order.get_portal_url())
|
||||
|
||||
@@ -57,15 +57,15 @@
|
||||
<!-- Template and notification section -->
|
||||
<data noupdate="1">
|
||||
<template id="" inherit_id="mail.mail_notification_paynow" name="oezirhgoi">
|
||||
<xpath expr="//t[@t-set='access_code']" position="after">
|
||||
<t t-set="access_code" t-value="(record._name == 'sale.order' and record.state in ('draft', 'sent') and record.get_portal_confirmation_action()) or access_code"/>
|
||||
</xpath>
|
||||
<xpath expr="//t[@t-set='access_name']" position="after">
|
||||
<t t-if="record._name == 'sale.order' and access_code != 'view'">
|
||||
<t t-set="access_name">Accept and sign online</t>
|
||||
</t>
|
||||
<t t-if="record._name == 'sale.order' and access_code == 'sign' and record.has_to_be_paid()">
|
||||
<t t-set="access_name">Sign and pay online</t>
|
||||
<t t-if="record._name == 'sale.order'">
|
||||
<t t-if="record.has_to_be_signed()">
|
||||
<t t-if="record.has_to_be_paid()" t-set="access_name">Sign and pay online</t>
|
||||
<t t-else="" t-set="access_name">Accept and sign online</t>
|
||||
</t>
|
||||
<t t-elif="record.has_to_be_paid()">
|
||||
<t t-set="access_name">Pay online</t>
|
||||
</t>
|
||||
</t>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
+56
-16
@@ -14,6 +14,8 @@ from odoo.tools import float_is_zero, float_compare
|
||||
|
||||
from odoo.addons import decimal_precision as dp
|
||||
|
||||
from werkzeug.urls import url_encode
|
||||
|
||||
|
||||
class SaleOrder(models.Model):
|
||||
_name = "sale.order"
|
||||
@@ -28,6 +30,12 @@ class SaleOrder(models.Model):
|
||||
return fields.Date.to_string(datetime.now() + timedelta(days))
|
||||
return False
|
||||
|
||||
def _get_default_require_signature(self):
|
||||
return self.env.user.company_id.portal_confirmation_sign
|
||||
|
||||
def _get_default_require_payment(self):
|
||||
return self.env.user.company_id.portal_confirmation_pay
|
||||
|
||||
@api.depends('order_line.price_total')
|
||||
def _amount_all(self):
|
||||
"""
|
||||
@@ -136,6 +144,12 @@ class SaleOrder(models.Model):
|
||||
validity_date = fields.Date(string='Validity', readonly=True, copy=False, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]},
|
||||
help="Validity date of the quotation, after this date, the customer won't be able to validate the quotation online.", default=_default_validity_date)
|
||||
is_expired = fields.Boolean(compute='_compute_is_expired', string="Is expired")
|
||||
require_signature = fields.Boolean('Online Signature', default=_get_default_require_signature, readonly=True,
|
||||
states={'draft': [('readonly', False)], 'sent': [('readonly', False)]},
|
||||
help='Request a online signature to the customer in order to confirm orders automatically.')
|
||||
require_payment = fields.Boolean('Online Payment', default=_get_default_require_payment, readonly=True,
|
||||
states={'draft': [('readonly', False)], 'sent': [('readonly', False)]},
|
||||
help='Request an online payment to the customer in order to confirm orders automatically.')
|
||||
remaining_validity_days = fields.Integer(compute='_compute_remaining_validity_days', string="Remaining Validity Days")
|
||||
create_date = fields.Datetime(string='Creation Date', readonly=True, index=True, help="Date on which sales order is created.")
|
||||
confirmation_date = fields.Datetime(string='Confirmation Date', readonly=True, index=True, help="Date on which the sales order is confirmed.", oldname="date_confirm", copy=False)
|
||||
@@ -204,10 +218,7 @@ class SaleOrder(models.Model):
|
||||
def _compute_is_expired(self):
|
||||
today = fields.Date.today()
|
||||
for order in self:
|
||||
if order.validity_date and order.validity_date < today:
|
||||
order.is_expired = True
|
||||
else:
|
||||
order.is_expired = False
|
||||
order.is_expired = order.state == 'sent' and order.validity_date and order.validity_date < today
|
||||
|
||||
@api.multi
|
||||
@api.depends('order_line.customer_lead', 'confirmation_date', 'order_line.state')
|
||||
@@ -728,19 +739,12 @@ class SaleOrder(models.Model):
|
||||
|
||||
return report_pages
|
||||
|
||||
def get_portal_confirmation_action(self):
|
||||
if self.company_id.portal_confirmation_sign and not self.signature:
|
||||
return 'sign'
|
||||
if self.company_id.portal_confirmation_pay:
|
||||
return 'pay'
|
||||
def has_to_be_signed(self, also_in_draft=False):
|
||||
return (self.state == 'sent' or (self.state == 'draft' and also_in_draft)) and not self.is_expired and self.require_signature and not self.signature and self.team_id.team_type != 'website'
|
||||
|
||||
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
|
||||
def has_to_be_paid(self, also_in_draft=False):
|
||||
transaction = self.get_portal_last_transaction()
|
||||
return (self.state == 'sent' or (self.state == 'draft' and also_in_draft)) and not self.is_expired and self.require_payment and transaction.state != 'done' and self.amount_total
|
||||
|
||||
@api.multi
|
||||
def _notify_get_groups(self, message, groups):
|
||||
@@ -824,6 +828,15 @@ class SaleOrder(models.Model):
|
||||
|
||||
return transaction
|
||||
|
||||
@api.multi
|
||||
def preview_sale_order(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'target': 'self',
|
||||
'url': self.get_portal_url(),
|
||||
}
|
||||
|
||||
def _force_lines_to_invoice_policy_order(self):
|
||||
for line in self.order_line:
|
||||
if self.state in ['sale', 'done']:
|
||||
@@ -853,6 +866,33 @@ class SaleOrder(models.Model):
|
||||
self.ensure_one()
|
||||
return '%s %s' % (self.type_name, self.name)
|
||||
|
||||
@api.multi
|
||||
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()
|
||||
user = access_uid and self.env['res.users'].sudo().browse(access_uid) or self.env.user
|
||||
|
||||
if not self.sale_order_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': self.get_portal_url(),
|
||||
'target': 'self',
|
||||
'res_id': self.id,
|
||||
}
|
||||
|
||||
def _get_share_url(self, redirect=False, signup_partner=False, pid=None):
|
||||
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 self.get_portal_url(query_string='&%s' % auth_param)
|
||||
return super(SaleOrder, self)._get_share_url(redirect, signup_partner, pid)
|
||||
|
||||
@api.multi
|
||||
def _get_payment_type(self):
|
||||
self.ensure_one()
|
||||
return 'form_save' if self.require_payment else 'form'
|
||||
|
||||
|
||||
class SaleOrderLine(models.Model):
|
||||
_name = 'sale.order.line'
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
<span>Disc.(%)</span>
|
||||
<t t-set="colspan" t-value="colspan+1"/>
|
||||
</th>
|
||||
<th class="text-left">Taxes</th>
|
||||
<th class="text-right">Taxes</th>
|
||||
<th class="text-right">
|
||||
<t groups="account.group_show_line_subtotals_tax_excluded">Amount</t>
|
||||
<t groups="account.group_show_line_subtotals_tax_included">Total Price</t>
|
||||
@@ -107,7 +107,7 @@
|
||||
<td t-if="display_discount" class="text-right" groups="sale.group_discount_per_so_line">
|
||||
<span t-field="line.discount"/>
|
||||
</td>
|
||||
<td class="text-left">
|
||||
<td class="text-right">
|
||||
<span t-esc="', '.join(map(lambda x: (x.description or x.name), line.tax_id))"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
|
||||
@@ -131,36 +131,23 @@
|
||||
<t t-set="title">
|
||||
<h2 class="mb-0"><b t-field="sale_order.amount_total"/> </h2>
|
||||
</t>
|
||||
|
||||
<t t-set="entries">
|
||||
<ul class="list-group list-group-flush flex-wrap flex-row flex-lg-column">
|
||||
<li class="list-group-item flex-grow-1">
|
||||
<a role="button" class="btn btn-block btn-secondary" target="_blank" t-att-href="sale_order.get_portal_url(report_type='pdf')"><i class="fa fa-print mr-1"/>Print</a>
|
||||
|
||||
<t t-if="sale_order.state in ('draft', 'sent', 'waiting_date')">
|
||||
<t t-set="to_accept" t-value="not sale_order.is_expired and not sale_order.signature and sale_order.has_to_be_signed()"/>
|
||||
<t t-set="to_accept_and_pay" t-value="not sale_order.is_expired and sale_order.has_to_be_paid()"/>
|
||||
|
||||
<t t-if="to_accept or to_accept_and_pay or sale_order.is_expired">
|
||||
<a role="button" t-if="to_accept" class="btn btn-primary btn-block" data-toggle="modal" data-target="#modalaccept" href="#">
|
||||
<i class="fa fa-check"/> Accept & Sign
|
||||
</a>
|
||||
<a role="button" t-elif="to_accept_and_pay" class="btn btn-success btn-block" data-toggle="modal" data-target="#modalaccept" href="#">
|
||||
<i class="fa fa-check"/> <t t-if="not sale_order.signature">Accept & </t>Pay
|
||||
</a>
|
||||
<t t-if="sale_order.is_expired">
|
||||
<div class="alert alert-warning small mt-3 mb-1 py-1 px-2"><b class="d-block mb-1">This offer expired!</b>
|
||||
<a role="button" href="#discussion" class="btn btn-info btn-block py-1 o_normalwhitespace"> Get a new quotation.</a>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-if="need_payment or (sale_order.state in ('sale') and sale_order.has_to_be_paid() and tx_state != 'done')">
|
||||
<a role="button" class="btn btn-success btn-block" data-toggle="modal" data-target="#modalaccept" href="#">
|
||||
<i class="fa fa-check"></i> Pay Now
|
||||
</a>
|
||||
</t>
|
||||
<a t-if="sale_order.has_to_be_signed(True)" role="button" t-attf-class="btn btn-{{ 'primary' if sale_order.has_to_be_paid() else 'success'}} btn-block mb8" data-toggle="modal" data-target="#modalaccept" href="#">
|
||||
<i class="fa fa-check"/> Accept & Sign
|
||||
</a>
|
||||
<a t-elif="sale_order.has_to_be_paid(True)" role="button" class="btn btn-success btn-block mb8" data-toggle="modal" data-target="#modalaccept" href="#">
|
||||
<i class="fa fa-check"/> <t t-if="not sale_order.signature">Accept & Pay</t><t t-else="">Pay now</t>
|
||||
</a>
|
||||
<div class="o_download_pdf btn-toolbar flex-sm-nowrap">
|
||||
<div class="btn-group flex-grow-1 mr-1 mb-1">
|
||||
<a class="btn btn-secondary btn-block o_download_btn" t-att-href="sale_order.get_portal_url(report_type='pdf', download=True)" title="Download"><i class="fa fa-download"/> Download</a>
|
||||
</div>
|
||||
<div class="btn-group flex-grow-1 mb-1">
|
||||
<a class="btn btn-secondary btn-block o_print_btn o_portal_invoice_print" t-att-href="sale_order.get_portal_url(report_type='pdf')" id="print_invoice_report" title="Print" target="_blank"><i class="fa fa-print"/> Print</a>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="navspy list-group-item pl-0 d-none d-lg-block" t-ignore="true" role="complementary">
|
||||
@@ -168,34 +155,26 @@
|
||||
</li>
|
||||
|
||||
<t t-if="not sale_order.is_expired and sale_order.state in ['draft', 'sent']">
|
||||
<input type="hidden" t-att-value="sale_order.validity_date" id="validity_date"/>
|
||||
<li t-if="sale_order.remaining_validity_days > 0" class="list-group-item">
|
||||
<small><b class="text-muted">This offer expires in</b></small>
|
||||
<div class="day_counter">
|
||||
<i class="fa fa-clock-o" aria-title="Dates" title="Dates"></i>
|
||||
<t t-esc="sale_order.remaining_validity_days"/>
|
||||
day<t t-if="sale_order.remaining_validity_days > 1">s</t>
|
||||
<t t-if="sale_order.remaining_validity_days > 1">days</t><t t-else="">day</t>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li t-if="sale_order.amount_undiscounted > sale_order.amount_untaxed" class="list-group-item">
|
||||
<small><b class="text-muted">Your advantage</b></small>
|
||||
<small>
|
||||
<b t-field="sale_order.amount_undiscounted"
|
||||
t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'
|
||||
style="text-decoration: line-through"
|
||||
class="d-block mt-1"/>
|
||||
</small>
|
||||
<t t-if="sale_order.amount_untaxed == sale_order.amount_total">
|
||||
<small>
|
||||
<b t-field="sale_order.amount_undiscounted"
|
||||
t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'
|
||||
style="text-decoration: line-through"
|
||||
class="d-block mt-1"/>
|
||||
</small>
|
||||
<h4 t-field="sale_order.amount_total" class="text-success"/>
|
||||
</t>
|
||||
<t t-if="sale_order.amount_untaxed != sale_order.amount_total">
|
||||
<small>
|
||||
<strong t-field="sale_order.amount_undiscounted"
|
||||
t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'
|
||||
style="text-decoration: line-through"
|
||||
class="d-block mt-1"/>
|
||||
</small>
|
||||
<t t-else="">
|
||||
<h4 t-field="sale_order.amount_untaxed" class="text-success mb-0"/>
|
||||
<small>(<span t-field="sale_order.amount_total"/> Incl. tax)</small>
|
||||
</t>
|
||||
@@ -222,34 +201,9 @@
|
||||
<!-- Page content -->
|
||||
<div id="quote_content" class="col-12 col-lg justify-content-end">
|
||||
|
||||
<div class="alert alert-success alert-dismissable" t-if="message==1" role="status">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">&times;</button>
|
||||
Your message has been successfully sent!
|
||||
</div>
|
||||
<div class="alert alert-warning alert-dismissable" t-if="message==3" role="status">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">&times;</button>
|
||||
This order has been validated. Thanks for your trust
|
||||
and do not hesitate to <a href="#discussion">contact us</a> for
|
||||
any question.
|
||||
</div>
|
||||
<t t-if="sale_order.transaction_ids">
|
||||
<t t-call="payment.payment_confirmation_status">
|
||||
<t t-set="payment_tx_id" t-value="sale_order.get_portal_last_transaction()"/>
|
||||
</t>
|
||||
</t>
|
||||
<div class="alert alert-warning alert-dismissable" t-if="message==4 and sale_order.state != 'sent'" role="alert">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">&times;</button>
|
||||
This order
|
||||
<t t-if="sale_order.state=='draft'">has not yet been sent!</t>
|
||||
<t t-if="sale_order.state=='cancel'">has already been cancelled!</t>
|
||||
<t t-if="sale_order.state not in ('cancel','draft')">has already been validated!</t>
|
||||
You can <a href="#discussion">contact us</a> for
|
||||
any questions.
|
||||
</div>
|
||||
|
||||
<!-- modal relative to the actions Accept/Reject/Cancel -->
|
||||
<!-- modal relative to the actions sign and pay -->
|
||||
<div role="dialog" class="modal fade" id="modalaccept">
|
||||
<div class="modal-dialog" t-if="not sale_order.signature and sale_order.has_to_be_signed()">
|
||||
<div class="modal-dialog" t-if="sale_order.has_to_be_signed(True)">
|
||||
<form id="accept" method="POST" t-att-data-order-id="sale_order.id" t-att-data-token="sale_order.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">
|
||||
@@ -258,11 +212,12 @@
|
||||
</header>
|
||||
<main class="modal-body" id="sign-dialog">
|
||||
<p>
|
||||
<span>I agree that by signing this proposal, I accept it on the behalf of </span>
|
||||
<b t-field="sale_order.partner_id.commercial_partner_id"/>
|
||||
<span>, for an amount of </span>
|
||||
<b data-id="total_amount" t-field="sale_order.amount_total"/>
|
||||
<span>with payment terms: </span><b t-field="sale_order.payment_term_id"/>.
|
||||
<span>By signing this proposal, I agree to the following terms:</span>
|
||||
<ul>
|
||||
<li><span>Accepted on the behalf of:</span> <b t-field="sale_order.partner_id.commercial_partner_id"/></li>
|
||||
<li><span>For an amount of:</span> <b data-id="total_amount" t-field="sale_order.amount_total"/></li>
|
||||
<li t-if="sale_order.payment_term_id"><span>With payment terms:</span> <b t-field="sale_order.payment_term_id"/></li>
|
||||
</ul>
|
||||
</p>
|
||||
<t t-call="portal.portal_signature">
|
||||
<t t-set="object" t-value="sale_order"/>
|
||||
@@ -274,36 +229,36 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="modal-dialog modal-content" t-if="not (sale_order.has_to_be_signed() and not sale_order.signature) and (sale_order.has_to_be_paid() or need_payment)">
|
||||
<div class="modal-dialog modal-content" t-if="not sale_order.has_to_be_signed(True) and sale_order.has_to_be_paid(True)">
|
||||
<header class="modal-header">
|
||||
<h4 class="modal-title">Validate Order</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">&times;</button>
|
||||
</header>
|
||||
<main class="modal-body" id="sign-dialog">
|
||||
<p>
|
||||
<span>I agree that by paying this proposal, I accept it on the behalf of </span>
|
||||
<b t-field="sale_order.partner_id.commercial_partner_id"/><span>
|
||||
, for an amount of </span>
|
||||
<b data-id="total_amount" t-field="sale_order.amount_total"/>
|
||||
<span>with payment terms: </span><b t-field="sale_order.payment_term_id"/>.
|
||||
<span>By paying this proposal, I agree to the following terms:</span>
|
||||
<ul>
|
||||
<li><span>Accepted on the behalf of:</span> <b t-field="sale_order.partner_id.commercial_partner_id"/></li>
|
||||
<li><span>For an amount of:</span> <b data-id="total_amount" t-field="sale_order.amount_total"/></li>
|
||||
<li t-if="sale_order.payment_term_id"><span>With payment terms:</span> <b t-field="sale_order.payment_term_id"/></li>
|
||||
</ul>
|
||||
</p>
|
||||
</main>
|
||||
<footer class="modal-footer">
|
||||
<div t-if="pms or s2s_acquirers or form_acquirers" id="payment_method" class="text-left">
|
||||
<h3 class="mb24">Pay with</h3>
|
||||
<t t-call="payment.payment_tokens_list">
|
||||
<t t-set="mode" t-value="'payment'"/>
|
||||
<t t-set="submit_txt" t-value="'Pay & Confirm'"/>
|
||||
<t t-set="submit_txt">Pay & Confirm</t>
|
||||
<t t-set="icon_class" t-value="'fa-lock'"/>
|
||||
<t t-set="form_action" t-value="sale_order.get_portal_url(suffix='/transaction/token')"/>
|
||||
<t t-set="prepare_tx_url" t-value="sale_order.get_portal_url(suffix='/transaction/')"/>
|
||||
<t t-set="access_token" t-value="sale_order.access_token"/>
|
||||
</t>
|
||||
</div>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- modal relative to the action reject -->
|
||||
<div role="dialog" class="modal fade" id="modaldecline">
|
||||
<div class="modal-dialog">
|
||||
<form id="decline" method="POST" t-attf-action="/my/orders/#{sale_order.id}/decline?access_token=#{sale_order.access_token}" class="modal-content">
|
||||
@@ -316,7 +271,7 @@
|
||||
<p>
|
||||
Tell us why you are refusing this quotation, this will help us improve our services.
|
||||
</p>
|
||||
<textarea rows="4" name="decline_message" placeholder="Your feedback....." class="form-control"/>
|
||||
<textarea rows="4" name="decline_message" required="" placeholder="Your feedback..." class="form-control" />
|
||||
</main>
|
||||
<footer class="modal-footer">
|
||||
<button type="submit" t-att-id="sale_order.id" class="btn btn-danger"><i class="fa fa-times"></i> Reject</button>
|
||||
@@ -326,41 +281,68 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-danger alert-dismissable d-print-none" t-if="sale_order.state == 'cancel'" role="alert">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="close">&times;</button>
|
||||
<strong>This quotation has been canceled.</strong> Contact us to get a new quotation.
|
||||
<!-- status messages -->
|
||||
<div t-if="message == 'sign_ok'" class="alert alert-success alert-dismissable d-print-none" role="status">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">&times;</button>
|
||||
<strong>Thank You!</strong><br/>
|
||||
<t t-if="message == 'sign_ok' and sale_order.state in ['sale', 'done']">Your order has been confirmed.</t>
|
||||
<t t-elif="message == 'sign_ok' and sale_order.has_to_be_paid()">Your order has been signed but still needs to be paid to be confirmed.</t>
|
||||
<t t-else="">Your order has been signed.</t>
|
||||
</div>
|
||||
|
||||
<div t-if="message == 'cant_reject' and sale_order.has_to_be_signed()" class="alert alert-danger alert-dismissable d-print-none" role="alert">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">&times;</button>
|
||||
Your order is not in a state to be rejected.
|
||||
</div>
|
||||
|
||||
<t t-if="sale_order.transaction_ids">
|
||||
<t t-call="payment.payment_confirmation_status">
|
||||
<t t-set="payment_tx_id" t-value="sale_order.get_portal_last_transaction()"/>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<div t-if="sale_order.state == 'cancel'" class="alert alert-danger alert-dismissable d-print-none" role="alert">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="close">&times;</button>
|
||||
<strong>This quotation has been canceled.</strong> <a role="button" href="#discussion"><i class="fa fa-comment"/> Contact us to get a new quotation.</a>
|
||||
</div>
|
||||
|
||||
<div t-if="sale_order.state == 'draft'" class="alert alert-warning alert-dismissable d-print-none" role="status">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="close">&times;</button>
|
||||
<strong>This is a draft quotation.</strong> <a role="button" href="#discussion"><i class="fa fa-comment"/> Contact us to get the final version.</a>
|
||||
</div>
|
||||
|
||||
<div t-if="sale_order.is_expired" class="alert alert-warning alert-dismissable d-print-none" role="alert">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="close">&times;</button>
|
||||
<strong>This offer expired!</strong> <a role="button" href="#discussion"><i class="fa fa-comment"/> Contact us to get a new quotation.</a>
|
||||
</div>
|
||||
|
||||
<!-- main content -->
|
||||
<div t-attf-class="card #{'pb-5' if report_type == 'html' else ''}">
|
||||
<div t-call="sale.sale_order_portal_content"/>
|
||||
</div>
|
||||
|
||||
<t t-if="sale_order.team_id.team_type != 'website'">
|
||||
<t t-if="sale_order.state in ('draft', 'sent', 'waiting_date')">
|
||||
<t t-set="to_accept" t-value="not sale_order.is_expired and not sale_order.signature and sale_order.has_to_be_signed()"/>
|
||||
<t t-set="to_accept_and_pay" t-value="not sale_order.is_expired and not sale_order.signature and sale_order.has_to_be_signed()"/>
|
||||
</t>
|
||||
<t t-set="to_pay" t-value="need_payment or (sale_order.state in ('sale') and sale_order.has_to_be_paid() and tx_state != 'done')"/>
|
||||
<!-- bottom actions -->
|
||||
<div t-if="sale_order.has_to_be_signed(True) or sale_order.has_to_be_paid(True)" class="row justify-content-center text-center d-print-none pt-1 pb-4">
|
||||
|
||||
<div t-if="to_accept or to_accept_and_pay or to_pay" class="row justify-content-center text-center d-print-none pt-1 pb-4">
|
||||
<t t-if="sale_order.has_to_be_signed(True)">
|
||||
<div class="col-sm-auto mt8">
|
||||
<a t-if="to_accept" role="button" class="btn btn-primary" data-toggle="modal" data-target="#modalaccept" href="#"><i class="fa fa-check"/> Accept & Sign</a>
|
||||
<a t-elif="to_accept_and_pay" role="button" class="btn btn-primary" data-toggle="modal" data-target="#modalaccept" href="#"><i class="fa fa-check"/> <t t-if="not sale_order.signature">Accept & </t>Pay</a>
|
||||
<a role="button" t-attf-class="btn btn-{{ 'primary' if sale_order.has_to_be_paid() else 'success'}}" data-toggle="modal" data-target="#modalaccept" href="#"><i class="fa fa-check"/> Accept & Sign</a>
|
||||
</div>
|
||||
<t t-if="to_accept or to_accept_and_pay">
|
||||
<div class="col-sm-auto mt8">
|
||||
<a role="button" class="btn btn-info" type="submit" href="#discussion"><i class="fa fa-comment"/> Feedback</a>
|
||||
</div>
|
||||
<div class="col-sm-auto mt8">
|
||||
<a role="button" class="btn btn-warning" data-toggle="modal" data-target="#modaldecline" href="#"> <i class="fa fa-times"/> Reject</a>
|
||||
</div>
|
||||
</t>
|
||||
<div t-if="to_pay" class="col-sm-auto mt8">
|
||||
<a role="button" class="btn btn-success" data-toggle="modal" data-target="#modalaccept" href="#"><i class="fa fa-check"/> Pay Now</a>
|
||||
<div class="col-sm-auto mt8">
|
||||
<a role="button" class="btn btn-secondary" href="#discussion"><i class="fa fa-comment"/> Feedback</a>
|
||||
</div>
|
||||
<div class="col-sm-auto mt8">
|
||||
<a role="button" class="btn btn-danger" data-toggle="modal" data-target="#modaldecline" href="#"> <i class="fa fa-times"/> Reject</a>
|
||||
</div>
|
||||
</t>
|
||||
<div t-elif="sale_order.has_to_be_paid(True)" class="col-sm-auto mt8">
|
||||
<a role="button" class="btn btn-success" data-toggle="modal" data-target="#modalaccept" href="#">
|
||||
<i class="fa fa-check"/> <t t-if="not sale_order.signature">Accept & Pay</t><t t-else="">Pay now</t>
|
||||
</a>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
<!-- chatter -->
|
||||
<div id="sale_order_communication" class="mt-4">
|
||||
<h2>Communication</h2>
|
||||
<t t-call="portal.message_thread">
|
||||
@@ -454,7 +436,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section id="details" style="page-break-inside: auto;">
|
||||
<section id="details" style="page-break-inside: auto;" class="mt32">
|
||||
<h3 id="details">Details</h3>
|
||||
|
||||
<t t-set="display_discount" t-value="True in [line.discount > 0 for line in sale_order.order_line]"/>
|
||||
@@ -466,7 +448,7 @@
|
||||
<th class="text-left">Products</th>
|
||||
<th class="text-right">Quantity</th>
|
||||
<th t-attf-class="text-right {{ 'd-none d-sm-table-cell' if report_type == 'html' else '' }}">Unit Price</th>
|
||||
<th t-if="display_discount" class="text-left">
|
||||
<th t-if="display_discount" t-attf-class="text-right {{ 'd-none d-sm-table-cell' if report_type == 'html' else '' }}">
|
||||
<span>Discount</span>
|
||||
<t t-set="colspan" t-value="colspan+1"/>
|
||||
</th>
|
||||
@@ -506,7 +488,7 @@
|
||||
<t t-esc="(1-line.discount / 100.0) * line.price_unit" t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'/>
|
||||
</div>
|
||||
</td>
|
||||
<td t-if="display_discount" class="text-left">
|
||||
<td t-if="display_discount" t-attf-class="text-right {{ 'd-none d-sm-table-cell' if report_type == 'html' else '' }}">
|
||||
<strong t-if="line.discount" class="text-info">
|
||||
<t t-esc="((line.discount % 1) and '%s' or '%d') % line.discount"/>% discount
|
||||
</strong>
|
||||
|
||||
@@ -236,6 +236,7 @@
|
||||
<button name="action_confirm"
|
||||
string="Confirm" type="object"
|
||||
attrs="{'invisible': [('state', 'not in', ['draft'])]}"/>
|
||||
<button name="preview_sale_order" type="object" string="Preview"/>
|
||||
<button name="action_quotation_send" type="object" string="Send PRO-FORMA Invoice" groups="sale.group_proforma_sales" attrs="{'invisible': ['|', ('state', '=', 'draft'), ('invoice_count','>=',1)]}" context="{'proforma': True}"/>
|
||||
<button name="print_quotation" string="Print" type="object" states="sent,sale" class="o_sale_print"/>
|
||||
<button name="action_quotation_send" string="Send by Email" type="object" states="sent,sale"/>
|
||||
@@ -543,6 +544,8 @@
|
||||
<field name="user_id"/>
|
||||
<field name="team_id" options="{'no_create': True}"/>
|
||||
<field name="client_order_ref"/>
|
||||
<field name="require_signature"/>
|
||||
<field name="require_payment"/>
|
||||
<field name="reference" readonly="1" attrs="{'invisible': [('reference', '=', False)]}"/>
|
||||
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
|
||||
<field name="analytic_account_id" context="{'default_partner_id':partner_invoice_id, 'default_name':name}" attrs="{'readonly': [('invoice_count','!=',0),('state','=','sale')]}" groups="analytic.group_analytic_accounting" force_save="1"/>
|
||||
|
||||
@@ -1,66 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import http, _
|
||||
from odoo import http
|
||||
from odoo.exceptions import AccessError, MissingError
|
||||
from odoo.http import request
|
||||
from odoo.addons.sale.controllers.portal import CustomerPortal
|
||||
from odoo.addons.portal.controllers.mail import _message_post_helper
|
||||
|
||||
|
||||
class CustomerPortal(CustomerPortal):
|
||||
|
||||
@http.route(['/my/orders/<int:order_id>/accept'], type='json', auth="public", website=True)
|
||||
def portal_quote_accept(self, res_id, access_token=None, partner_name=None, signature=None, order_id=None):
|
||||
try:
|
||||
order_sudo = self._document_check_access('sale.order', res_id, access_token=access_token)
|
||||
except (AccessError, MissingError):
|
||||
return {'error': _('Invalid order')}
|
||||
if order_sudo.state != 'sent':
|
||||
return {'error': _('Order is not in a state requiring customer validation.')}
|
||||
|
||||
if not order_sudo.has_to_be_signed() and not order_sudo.has_to_be_paid():
|
||||
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=[('%s.pdf' % order_sudo.name, pdf)],
|
||||
**({'token': access_token} if access_token else {}))
|
||||
|
||||
return {
|
||||
'success': success_message,
|
||||
'redirect_url': order_sudo.get_portal_url(),
|
||||
}
|
||||
|
||||
@http.route(['/my/orders/<int:order_id>/decline'], type='http', auth="public", methods=['POST'], website=True)
|
||||
def decline(self, order_id, access_token=None, **post):
|
||||
try:
|
||||
order_sudo = self._document_check_access('sale.order', order_id, access_token=access_token)
|
||||
except (AccessError, MissingError):
|
||||
return request.redirect('/my')
|
||||
|
||||
if order_sudo.state != 'sent':
|
||||
return request.redirect(order_sudo.get_portal_url(query_string="&message=4"))
|
||||
order_sudo.action_cancel()
|
||||
message = post.get('decline_message')
|
||||
if message:
|
||||
_message_post_helper(message=message, res_id=order_id, res_model='sale.order', **{'token': access_token} if access_token else {})
|
||||
return request.redirect(order_sudo.get_portal_url())
|
||||
|
||||
@http.route(['/my/orders/<int:order_id>/update_line'], type='json', auth="public", website=True)
|
||||
def update(self, line_id, remove=False, unlink=False, order_id=None, access_token=None, **post):
|
||||
try:
|
||||
@@ -96,70 +44,3 @@ class CustomerPortal(CustomerPortal):
|
||||
option_sudo.add_option_to_order()
|
||||
|
||||
return request.redirect(option_sudo.order_id.get_portal_url(anchor='details'))
|
||||
|
||||
# note dbo: website_sale code
|
||||
@http.route(['/my/orders/<int:order_id>/transaction/'], type='json', auth="public", website=True)
|
||||
def payment_transaction_token(self, acquirer_id, order_id, save_token=False, access_token=None, **kwargs):
|
||||
""" Json method that creates a payment.transaction, used to create a
|
||||
transaction when the user clicks on 'pay now' button. After having
|
||||
created the transaction, the event continues and the user is redirected
|
||||
to the acquirer website.
|
||||
|
||||
:param int acquirer_id: id of a payment.acquirer record. If not set the
|
||||
user is redirected to the checkout page
|
||||
"""
|
||||
# Ensure a payment acquirer is selected
|
||||
if not acquirer_id:
|
||||
return False
|
||||
|
||||
try:
|
||||
acquirer_id = int(acquirer_id)
|
||||
except:
|
||||
return False
|
||||
|
||||
order = request.env['sale.order'].sudo().browse(order_id)
|
||||
if not order or not order.order_line:
|
||||
return False
|
||||
|
||||
# Create transaction
|
||||
vals = {
|
||||
'acquirer_id': acquirer_id,
|
||||
'type': order._get_payment_type(),
|
||||
}
|
||||
|
||||
transaction = order._create_payment_transaction(vals)
|
||||
|
||||
return transaction.render_sale_button(
|
||||
order,
|
||||
order.get_portal_url(),
|
||||
submit_txt=_('Pay & Confirm'),
|
||||
render_values={
|
||||
'type': order._get_payment_type(),
|
||||
'alias_usage': _('If we store your payment information on our server, subscription payments will be made automatically.'),
|
||||
}
|
||||
)
|
||||
|
||||
@http.route('/my/orders/<int:order_id>/transaction/token', type='http', auth='public', website=True)
|
||||
def payment_token(self, order_id, pm_id=None, **kwargs):
|
||||
|
||||
order = request.env['sale.order'].sudo().browse(order_id)
|
||||
if not order:
|
||||
return request.redirect("/my/orders")
|
||||
if not order.order_line or pm_id is None:
|
||||
return request.redirect(order.get_portal_url())
|
||||
|
||||
# try to convert pm_id into an integer, if it doesn't work redirect the user to the quote
|
||||
try:
|
||||
pm_id = int(pm_id)
|
||||
except ValueError:
|
||||
return request.redirect(order.get_portal_url())
|
||||
|
||||
# Create transaction
|
||||
vals = {
|
||||
'payment_token_id': pm_id,
|
||||
'type': 'server2server',
|
||||
}
|
||||
|
||||
order._create_payment_transaction(vals)
|
||||
|
||||
return request.redirect(order.get_portal_url())
|
||||
|
||||
@@ -7,8 +7,6 @@ from odoo import api, fields, models, _
|
||||
from odoo.addons import decimal_precision as dp
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
from werkzeug.urls import url_encode
|
||||
|
||||
|
||||
class SaleOrder(models.Model):
|
||||
_inherit = 'sale.order'
|
||||
@@ -21,12 +19,6 @@ class SaleOrder(models.Model):
|
||||
'sale.order.option', 'order_id', 'Optional Products Lines',
|
||||
copy=True, readonly=True,
|
||||
states={'draft': [('readonly', False)], 'sent': [('readonly', False)]})
|
||||
require_signature = fields.Boolean('Online Signature',
|
||||
states={'sale': [('readonly', True)], 'done': [('readonly', True)]},
|
||||
help='Request a online signature to the customer in order to confirm orders automatically.')
|
||||
require_payment = fields.Boolean('Online Payment',
|
||||
states={'sale': [('readonly', True)], 'done': [('readonly', True)]},
|
||||
help='Request an online payment to the customer in order to confirm orders automatically.')
|
||||
|
||||
@api.multi
|
||||
@api.returns('self', lambda value: value.id)
|
||||
@@ -65,8 +57,8 @@ class SaleOrder(models.Model):
|
||||
@api.onchange('sale_order_template_id')
|
||||
def onchange_sale_order_template_id(self):
|
||||
if not self.sale_order_template_id:
|
||||
self.require_signature = False
|
||||
self.require_payment = False
|
||||
self.require_signature = self._get_default_require_signature()
|
||||
self.require_payment = self._get_default_require_payment()
|
||||
return
|
||||
template = self.sale_order_template_id.with_context(lang=self.partner_id.lang)
|
||||
|
||||
@@ -114,56 +106,6 @@ class SaleOrder(models.Model):
|
||||
if template.note:
|
||||
self.note = template.note
|
||||
|
||||
@api.multi
|
||||
def preview_sale_order(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'target': 'self',
|
||||
'url': self.get_portal_url(),
|
||||
}
|
||||
|
||||
@api.multi
|
||||
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()
|
||||
user = access_uid and self.env['res.users'].sudo().browse(access_uid) or self.env.user
|
||||
|
||||
if not self.sale_order_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': self.get_portal_url(),
|
||||
'target': 'self',
|
||||
'res_id': self.id,
|
||||
}
|
||||
|
||||
def _get_share_url(self, redirect=False, signup_partner=False, pid=None):
|
||||
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 self.get_portal_url(query_string='&%s' % auth_param)
|
||||
return super(SaleOrder, self)._get_share_url(redirect, signup_partner, pid)
|
||||
|
||||
def get_portal_confirmation_action(self):
|
||||
""" Template override default behavior of pay / sign chosen in sales settings """
|
||||
if self.sale_order_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.sale_order_template_id else res
|
||||
|
||||
def has_to_be_paid(self):
|
||||
res = super(SaleOrder, self).has_to_be_paid()
|
||||
return self.require_payment if self.sale_order_template_id else res
|
||||
|
||||
@api.multi
|
||||
def action_confirm(self):
|
||||
res = super(SaleOrder, self).action_confirm()
|
||||
@@ -172,11 +114,6 @@ class SaleOrder(models.Model):
|
||||
self.sale_order_template_id.mail_template_id.send_mail(order.id)
|
||||
return res
|
||||
|
||||
@api.multi
|
||||
def _get_payment_type(self):
|
||||
self.ensure_one()
|
||||
return 'form_save' if self.require_payment else 'form'
|
||||
|
||||
|
||||
class SaleOrderLine(models.Model):
|
||||
_inherit = "sale.order.line"
|
||||
|
||||
@@ -6,10 +6,6 @@
|
||||
<field name="inherit_id" ref="sale.view_order_form"/>
|
||||
<field name="arch" type="xml">
|
||||
|
||||
<xpath expr="//header/button[@name='action_confirm'][1]" position="after">
|
||||
<button name="preview_sale_order" type="object" string="Preview"/>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//page/field[@name='order_line']/.." position="after">
|
||||
<page string="Optional Products">
|
||||
<field name="sale_order_option_ids">
|
||||
@@ -37,11 +33,6 @@
|
||||
/>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//field[@name='client_order_ref']" position="after">
|
||||
<field name="require_signature" attrs="{'invisible': [('sale_order_template_id', '=', False)]}"/>
|
||||
<field name="require_payment" attrs="{'invisible': [('sale_order_template_id', '=', False)]}"/>
|
||||
</xpath>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user