[FIX] various: clean up portal and reports (sales and invoices)
The goal of this commit is to fix the various views and reports of sales and invoices. ====== Portal ====== Fix breadcrumbs Fix BS4 issues, alignment of various elements Better auto resize of the invoice iframe Fix invoice list for draft invoices Fix previous/next document pagers Remove unnecessary monetary widgets (not needed when using t-field if the field is Monetary) Standardize invoice and sale routes: - especially when related to displaying: html, pdf (print and download), text - by using the get_portal_url() also for invoices Add the total on the left column of sales order ================ Section and note ================ Improve general style: - notes in italics - sections: less padding, darker background, no borders ========= Demo Data ========= Fix sales order demo data to appear for the admin (instead of system) PR: #25997 task-1869469
This commit is contained in:
@@ -1,9 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import re
|
||||
from werkzeug.exceptions import NotFound
|
||||
|
||||
from odoo import http, _
|
||||
from odoo.addons.portal.controllers.portal import CustomerPortal, pager as portal_pager
|
||||
from odoo.exceptions import AccessError, MissingError
|
||||
@@ -31,11 +28,9 @@ class PortalAccount(CustomerPortal):
|
||||
}
|
||||
return self._get_page_view_values(invoice, access_token, values, 'my_invoices_history', False, **kwargs)
|
||||
|
||||
|
||||
@http.route(['/my/invoices', '/my/invoices/page/<int:page>'], type='http', auth="user", website=True)
|
||||
def portal_my_invoices(self, page=1, date_begin=None, date_end=None, sortby=None, **kw):
|
||||
values = self._prepare_portal_layout_values()
|
||||
partner = request.env.user.partner_id
|
||||
AccountInvoice = request.env['account.invoice']
|
||||
|
||||
domain = []
|
||||
@@ -82,42 +77,18 @@ class PortalAccount(CustomerPortal):
|
||||
return request.render("account.portal_my_invoices", values)
|
||||
|
||||
@http.route(['/my/invoices/<int:invoice_id>'], type='http', auth="public", website=True)
|
||||
def portal_my_invoice_detail(self, invoice_id, access_token=None, **kw):
|
||||
def portal_my_invoice_detail(self, invoice_id, access_token=None, report_type=None, download=False, **kw):
|
||||
try:
|
||||
invoice_sudo = self._document_check_access('account.invoice', invoice_id, access_token)
|
||||
except (AccessError, MissingError):
|
||||
return request.redirect('/my')
|
||||
|
||||
if report_type in ('html', 'pdf', 'text'):
|
||||
return self._show_report(model=invoice_sudo, report_type=report_type, report_ref='account.account_invoices', download=download)
|
||||
|
||||
values = self._invoice_get_page_view_values(invoice_sudo, access_token, **kw)
|
||||
return request.render("account.portal_invoice_page", values)
|
||||
|
||||
@http.route([
|
||||
'/my/invoices/pdf/<int:invoice_id>',
|
||||
'/my/invoices/html/<int:invoice_id>/<string:access_token>'
|
||||
], type='http', auth="public", website=True)
|
||||
def portal_my_invoice_report(self, invoice_id, access_token=None, **kw):
|
||||
try:
|
||||
invoice_sudo = self._document_check_access('account.invoice', invoice_id, access_token)
|
||||
except (AccessError, MissingError):
|
||||
return request.redirect('/my')
|
||||
|
||||
# print report as sudo, since it require access to taxes, payment term, ... and portal
|
||||
# does not have those access rights.
|
||||
accountInvoiceReport = request.env.ref('account.account_invoices').sudo()
|
||||
report_type = kw.get('report_type', 'pdf')
|
||||
method_name = 'render_qweb_%s' % (report_type)
|
||||
if hasattr(accountInvoiceReport, method_name):
|
||||
invoice_report = getattr(accountInvoiceReport, method_name)([invoice_sudo.id], data={'report_type': report_type})[0]
|
||||
reporthttpheaders = [
|
||||
('Content-Type', 'application/pdf' if report_type == 'pdf' else 'text/html'),
|
||||
('Content-Length', len(invoice_report)),
|
||||
]
|
||||
if report_type == 'pdf' and not kw.get('print'):
|
||||
filename = "%s.pdf" % (re.sub('\W+', '-', invoice_sudo._get_printed_report_name()))
|
||||
reporthttpheaders.append(('Content-Disposition', http.content_disposition(filename)))
|
||||
return request.make_response(invoice_report, headers=reporthttpheaders)
|
||||
raise NotFound()
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# My Home
|
||||
# ------------------------------------------------------------
|
||||
|
||||
@@ -479,7 +479,7 @@ class AccountInvoice(models.Model):
|
||||
sequence.number_next = int(result.group(2))
|
||||
|
||||
@api.multi
|
||||
def _get_printed_report_name(self):
|
||||
def _get_report_base_filename(self):
|
||||
self.ensure_one()
|
||||
return self.type == 'out_invoice' and self.state == 'draft' and _('Draft Invoice') or \
|
||||
self.type == 'out_invoice' and self.state in ('open','in_payment','paid') and _('Invoice - %s') % (self.number) or \
|
||||
@@ -1512,15 +1512,6 @@ class AccountInvoice(models.Model):
|
||||
len(res),
|
||||
) for r in res]
|
||||
|
||||
@api.multi
|
||||
def get_portal_url(self, suffix=None):
|
||||
"""
|
||||
Get a portal url for this invoice, including access_token.
|
||||
- suffix: string to append to the url, before the query string
|
||||
"""
|
||||
self.ensure_one()
|
||||
return self.access_url + '%s?access_token=%s' % (suffix if suffix else '', self._portal_ensure_token())
|
||||
|
||||
@api.multi
|
||||
def preview_invoice(self):
|
||||
self.ensure_one()
|
||||
|
||||
@@ -186,5 +186,5 @@ class ReportInvoiceWithPayment(models.AbstractModel):
|
||||
'doc_ids': docids,
|
||||
'doc_model': report.model,
|
||||
'docs': self.env[report.model].browse(docids),
|
||||
'type_html': data and data.get('report_type') == 'html',
|
||||
'report_type': data.get('report_type') if data else '',
|
||||
}
|
||||
|
||||
@@ -32,7 +32,9 @@ var AccountPortalSidebar = PortalSidebar.extend({
|
||||
var self = this;
|
||||
this._super.apply(this, arguments);
|
||||
var $invoiceHtml = this.$el.find('iframe#invoice_html');
|
||||
$invoiceHtml.on('load', self._onLoadIframe.bind(self, $invoiceHtml));
|
||||
var updateIframeSize = self._updateIframeSize.bind(self, $invoiceHtml);
|
||||
$invoiceHtml.on('load', updateIframeSize);
|
||||
$(window).on('resize', updateIframeSize);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -40,17 +42,17 @@ var AccountPortalSidebar = PortalSidebar.extend({
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Called when the iframe is load on custome portal
|
||||
* here we set height and width of html preview (iframe)
|
||||
* Called when the iframe is loaded or the window is resized on customer portal.
|
||||
* The goal is to expand the iframe height to display the full report without scrollbar.
|
||||
*
|
||||
* @private
|
||||
* @param {object} $el: the iframe
|
||||
*/
|
||||
_onLoadIframe: function ($el) {
|
||||
_updateIframeSize: function ($el) {
|
||||
var $body = $el.contents().find('body');
|
||||
// expand iframe height for display full report on the custome portal (no scrollbar in iframe)
|
||||
$el.height($body.scrollParent().height());
|
||||
// removed extra space in html preview (specially left and right margin)
|
||||
$body.width('100%');
|
||||
// Set it to 0 first to handle the case where scrollHeight is too big for its content.
|
||||
$el.height(0);
|
||||
$el.height($body[0].scrollHeight);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
// section and note on sale order and invoice.
|
||||
|
||||
table.o_section_and_note_list_view tr.o_data_row.o_is_line_note,
|
||||
table.o_section_and_note_list_view tr.o_data_row.o_is_line_note textarea[name="name"],
|
||||
div.oe_kanban_card.o_is_line_note {
|
||||
color: #999;
|
||||
font-style: italic;
|
||||
}
|
||||
table.o_section_and_note_list_view tr.o_data_row.o_is_line_section,
|
||||
div.oe_kanban_card.o_is_line_section {
|
||||
font-weight: bold;
|
||||
background-color: #EEE;
|
||||
background-color: #DDDDDD;
|
||||
}
|
||||
table.o_section_and_note_list_view tr.o_data_row.o_is_line_section {
|
||||
border-top: 1px solid #BBB;
|
||||
|
||||
@@ -490,8 +490,8 @@
|
||||
<field name="analytic_tag_ids" groups="analytic.group_analytic_tags" widget="many2many_tags" options="{'color_field': 'color'}"/>
|
||||
<field name="quantity"/>
|
||||
<field name="uom_id" groups="uom.group_uom"/>
|
||||
<field name="price_unit"/>
|
||||
<field name="discount" groups="base.group_no_one"/>
|
||||
<field name="price_unit" string="Price"/>
|
||||
<field name="discount" groups="base.group_no_one" string="Disc (%)"/>
|
||||
<field name="invoice_line_tax_ids" widget="many2many_tags" options="{'no_create': True}" context="{'type':parent.type, 'tree_view_ref': 'account.account_tax_view_tree', 'search_view_ref': 'account.account_tax_view_search'}"
|
||||
domain="[('type_tax_use','=','sale'),('company_id', '=', parent.company_id)]"/>
|
||||
<field name="price_subtotal" string="Subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
<t t-else="">Invoices</t>
|
||||
</li>
|
||||
<li t-if="invoice" class="breadcrumb-item">
|
||||
<t t-esc="invoice.number"/>
|
||||
<t t-esc="invoice.number" t-if="invoice.number"/>
|
||||
<t t-else="">Draft Invoice</t>
|
||||
</li>
|
||||
</xpath>
|
||||
</template>
|
||||
@@ -42,7 +43,10 @@
|
||||
<t t-foreach="invoices" t-as="invoice">
|
||||
<tr>
|
||||
<td>
|
||||
<a t-attf-href="/my/invoices/#{invoice.id}" t-att-title="invoice.number"><t t-esc="invoice.number"/></a>
|
||||
<a t-att-href="invoice.get_portal_url()" t-att-title="invoice.number">
|
||||
<t t-esc="invoice.number" t-if="invoice.number"/>
|
||||
<t t-else="">Draft Invoice</t>
|
||||
</a>
|
||||
</td>
|
||||
<td><span t-field="invoice.date_invoice"/></td>
|
||||
<td class='d-none d-md-table-cell'><span t-field="invoice.date_due"/></td>
|
||||
@@ -85,34 +89,47 @@
|
||||
</div>
|
||||
<div class="o_payment_button">
|
||||
<div class="text-center mb8 ml16 mr16 o_download_pdf">
|
||||
<a class="btn btn-secondary btn-block o_download_btn" t-att-href="'/my/invoices/pdf/%s?%s' % (invoice.id, keep_query())" title="Download"><i class="fa fa-download"/> Download</a>
|
||||
<a class="btn btn-secondary btn-block d-none d-lg-inline-block o_print_btn o_portal_invoice_print" t-att-href="'/my/invoices/pdf/%s?%s&print=true' % (invoice.id, keep_query())" id="print_invoice_report" title="Print"><i class="fa fa-print"/> Print</a>
|
||||
<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>
|
||||
<a class="btn btn-secondary btn-block d-none d-lg-inline-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>
|
||||
</div>
|
||||
</div>
|
||||
<hr t-if="invoice.user_id" class="mt0 mb0"/>
|
||||
<div class="pb8 ml16 mr16 container">
|
||||
<t t-if="invoice.user_id">
|
||||
<div><strong>Your Contact:</strong></div>
|
||||
<img class="rounded-circle mr4 float-left o_portal_contact_img" t-if="invoice.user_id.image" t-attf-src="data:image/png;base64,#{invoice.user_id.image}" alt="Contact"/>
|
||||
<img class="rounded-circle mr4 float-left o_portal_contact_img" t-if="not invoice.user_id.image" src="/web/static/src/img/placeholder.png" alt="Contact"/>
|
||||
<span t-field="invoice.user_id" t-options='{"widget": "contact", "fields": ["name", "phone"], "no_marker": True}'/>
|
||||
<a data-toggle="modal" href="#" data-target="#invoice_chatter">Contact us</a>
|
||||
<div class="d-flex h-100">
|
||||
<div class="align-self-center">
|
||||
<img class="rounded-circle mr4 float-left o_portal_contact_img" t-if="invoice.user_id.image" t-attf-src="data:image/png;base64,#{invoice.user_id.image}" alt="Contact"/>
|
||||
<img class="rounded-circle mr4 float-left o_portal_contact_img" t-if="not invoice.user_id.image" src="/web/static/src/img/placeholder.png" alt="Contact"/>
|
||||
</div>
|
||||
<div class="align-self-center">
|
||||
<span t-field="invoice.user_id" t-options='{"widget": "contact", "fields": ["name", "phone"], "no_marker": True}'/>
|
||||
<a data-toggle="modal" href="#" data-target="#invoice_chatter"><i class="fa fa-comment"></i> Contact us</a>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<div t-if="not invoice.user_id" class="text-center mr16">
|
||||
<a data-toggle="modal" href="#" data-target="#invoice_chatter">Contact us</a>
|
||||
<a data-toggle="modal" href="#" data-target="#invoice_chatter"><i class="fa fa-comment"></i> Contact us</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center d-none d-md-block mt4 o_portal_brand">
|
||||
Powered by <a target="_blank" href="http://www.odoo.com?utm_source=db&utm_medium=portal">Odoo</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center d-none d-md-block o_portal_brand">
|
||||
Powered by <a target="_blank" href="https://www.odoo.com?utm_source=db&utm_medium=portal">Odoo</a>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Page Content -->
|
||||
<div id="invoice_content" class="col-lg-9 o_portal_page_content d-print-none">
|
||||
<div t-ignore="true" class="float-md-right css_editable_mode_hidden d-print-none">
|
||||
<ul class="list-inline">
|
||||
<li groups="sales_team.group_sale_salesman" class="list-inline-item">
|
||||
<a role="button" class="btn btn-info mt16 mr16" t-att-href="'/web#return_label=Website&model=%s&id=%s&action=%s&view_type=form' % (invoice._name, invoice.id, invoice.env.ref('account.action_invoice_tree1').id)"><t t-if="invoice.state == 'draft'">Edit Invoice</t><t t-else="">Leave Portal</t></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<t t-if="error or warning" t-call="account.portal_invoice_error"/>
|
||||
<t t-if="success and (not error and not warning)" t-call="account.portal_invoice_success"/>
|
||||
<div class="o_portal_html_view">
|
||||
<iframe id="invoice_html" class="mt8 mb8" width="100%" height="100%" frameborder="0" scrolling="no" t-attf-src="/my/invoices/html/#{invoice.id}/#{invoice.access_token}?report_type=html"/>
|
||||
<iframe id="invoice_html" class="mt8 mb8" width="100%" height="100%" frameborder="0" scrolling="no" t-att-src="invoice.get_portal_url(report_type='html')"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
name="account.report_invoice_with_payments"
|
||||
file="account.report_invoice_with_payments"
|
||||
attachment="(object.state in ('open','in_payment','paid')) and ('INV'+(object.number or '').replace('/','')+'.pdf')"
|
||||
print_report_name="(object._get_printed_report_name())"
|
||||
print_report_name="(object._get_report_base_filename())"
|
||||
groups="account.group_account_invoice"
|
||||
/>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
name="account.report_invoice"
|
||||
file="account.report_invoice"
|
||||
attachment="(object.state in ('open','in_payment','paid')) and ('INV'+(object.number or '').replace('/','')+'.pdf')"
|
||||
print_report_name="(object._get_printed_report_name())"
|
||||
print_report_name="(object._get_report_base_filename())"
|
||||
/>
|
||||
|
||||
<report
|
||||
|
||||
@@ -5,9 +5,8 @@
|
||||
<t t-call="web.external_layout">
|
||||
<t t-set="o" t-value="o.with_context({'lang': lang})"/>
|
||||
<t t-set="address">
|
||||
<address t-field="o.partner_id"
|
||||
t-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": True}' />
|
||||
<div t-if="o.partner_id.vat" class="mt16"><t t-esc="o.company_id.country_id.vat_label or 'Tax ID'"/>: <span t-field="o.partner_id.vat"/></div>
|
||||
<address t-field="o.partner_id" t-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": True}' />
|
||||
<div t-if="o.partner_id.vat" class="mt16"><t t-esc="o.company_id.country_id.vat_label or 'Tax ID'"/>: <span t-field="o.partner_id.vat"/></div>
|
||||
</t>
|
||||
<div class="page">
|
||||
<h2>
|
||||
@@ -20,59 +19,55 @@
|
||||
<span t-field="o.number"/>
|
||||
</h2>
|
||||
|
||||
<!-- HTML Preview for mobile responsive -->
|
||||
<t t-set="o_grid_layout" t-value="'col-2'"/>
|
||||
<t t-if="type_html" t-set="o_grid_layout" t-value="'col-6 col-md-2'"/>
|
||||
<div class="row mt32 mb32">
|
||||
<div t-att-class="o_grid_layout" t-if="o.name" name="description">
|
||||
<div class="row col mt32 mb32">
|
||||
<div class="mr32" t-if="o.name" name="description">
|
||||
<strong>Description:</strong>
|
||||
<p t-field="o.name"/>
|
||||
</div>
|
||||
<div t-att-class="o_grid_layout" t-if="o.date_invoice" name="invoice_date">
|
||||
<div class="mr32" t-if="o.date_invoice" name="invoice_date">
|
||||
<strong>Invoice Date:</strong>
|
||||
<p t-field="o.date_invoice"/>
|
||||
</div>
|
||||
<div t-att-class="o_grid_layout" t-if="o.date_due and o.type == 'out_invoice' and o.state in ('open', 'in_payment', 'paid')" name="due_date">
|
||||
<div class="mr32" t-if="o.date_due and o.type == 'out_invoice' and o.state in ('open', 'in_payment', 'paid')" name="due_date">
|
||||
<strong>Due Date:</strong>
|
||||
<p t-field="o.date_due"/>
|
||||
</div>
|
||||
<div t-att-class="o_grid_layout" t-if="o.origin" name="origin">
|
||||
<div class="mr32" t-if="o.origin" name="origin">
|
||||
<strong>Source:</strong>
|
||||
<p t-field="o.origin"/>
|
||||
</div>
|
||||
<div t-att-class="o_grid_layout" t-if="o.partner_id.ref" name="customer_code">
|
||||
<div class="mr32" t-if="o.partner_id.ref" name="customer_code">
|
||||
<strong>Customer Code:</strong>
|
||||
<p t-field="o.partner_id.ref"/>
|
||||
</div>
|
||||
<div name="reference" t-att-class="o_grid_layout" t-if="o.reference">
|
||||
<div name="reference" class="mr32" t-if="o.reference">
|
||||
<strong>Reference:</strong>
|
||||
<p t-field="o.reference"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Is there a discount on at least one line? -->
|
||||
<t t-set="display_discount" t-value="any([l.discount for l in o.invoice_line_ids])"/>
|
||||
|
||||
<table t-attf-class="table table-sm" name="invoice_line_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<t t-set="colspan" t-value="6"/>
|
||||
<th><span>Description</span></th>
|
||||
<th class="d-none"><span>Source Document</span></th>
|
||||
<th class="text-right"><span>Quantity</span></th>
|
||||
<th class="text-right d-none d-md-table-cell"><span>Unit Price</span></th>
|
||||
<th t-if="display_discount" class="text-right d-none d-md-table-cell">
|
||||
<th class="text-left">Description</th>
|
||||
<th class="d-none text-left">Source Document</th>
|
||||
<th class="text-right">Quantity</th>
|
||||
<th t-attf-class="text-right {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}">Unit Price</th>
|
||||
<th t-if="display_discount" t-attf-class="text-right {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}">
|
||||
<span>Disc.(%)</span>
|
||||
<t t-set="colspan" t-value="colspan+1"/>
|
||||
</th>
|
||||
<th class="text-right d-none d-md-table-cell"><span>Taxes</span></th>
|
||||
<th t-attf-class="text-left {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}">Taxes</th>
|
||||
<th class="text-right">
|
||||
<span groups="account.group_show_line_subtotals_tax_excluded">Amount</span>
|
||||
<span groups="account.group_show_line_subtotals_tax_included">Total Price</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="invoice_tbody">
|
||||
<tbody class="invoice_tbody">
|
||||
<t t-set="current_subtotal" t-value="0"/>
|
||||
|
||||
<t t-foreach="o.invoice_line_ids" t-as="line">
|
||||
@@ -88,26 +83,18 @@
|
||||
<span t-field="line.quantity"/>
|
||||
<span t-field="line.uom_id" groups="uom.group_uom"/>
|
||||
</td>
|
||||
<td class="text-right d-none d-md-table-cell">
|
||||
<span t-field="line.price_unit"/>
|
||||
<td t-attf-class="text-right {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}">
|
||||
<span t-field="line.price_unit" t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
|
||||
</td>
|
||||
<td t-if="display_discount" class="text-right d-none d-md-table-cell">
|
||||
<td t-if="display_discount" t-attf-class="text-right {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}">
|
||||
<span t-field="line.discount"/>
|
||||
</td>
|
||||
<td class="text-right d-none d-md-table-cell">
|
||||
<td t-attf-class="text-left {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}">
|
||||
<span t-esc="', '.join(map(lambda x: (x.description or x.name), line.invoice_line_tax_ids))"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span
|
||||
t-field="line.price_subtotal"
|
||||
t-options='{"widget": "monetary", "display_currency": o.currency_id}'
|
||||
groups="account.group_show_line_subtotals_tax_excluded"
|
||||
/>
|
||||
<span
|
||||
t-field="line.price_total"
|
||||
t-options='{"widget": "monetary", "display_currency": o.currency_id}'
|
||||
groups="account.group_show_line_subtotals_tax_included"
|
||||
/>
|
||||
<span t-field="line.price_subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
|
||||
<span t-field="line.price_total" groups="account.group_show_line_subtotals_tax_included"/>
|
||||
</td>
|
||||
</t>
|
||||
<t t-if="line.display_type == 'line_section'">
|
||||
@@ -127,7 +114,8 @@
|
||||
<t t-if="current_section and (line_last or o.invoice_line_ids[line_index+1].display_type == 'line_section')">
|
||||
<tr class="is-subtotal text-right">
|
||||
<td t-att-colspan="colspan">
|
||||
<strong>Subtotal:</strong> <span
|
||||
<strong class="mr16">Subtotal</strong>
|
||||
<span
|
||||
t-esc="current_subtotal"
|
||||
t-options='{"widget": "monetary", "display_currency": o.currency_id}'
|
||||
/>
|
||||
@@ -139,13 +127,13 @@
|
||||
</table>
|
||||
|
||||
<div class="clearfix">
|
||||
<div id="total" class="row justify-content-end">
|
||||
<div t-attf-class="col-md-4 #{'col-4' if not type_html else ''}">
|
||||
<table class="table table-sm" style="min-width: 250px;max-width: 400px;">
|
||||
<div id="total" class="row">
|
||||
<div t-attf-class="#{'col-4' if report_type != 'html' else 'col-sm-7 col-md-5'} ml-auto">
|
||||
<table class="table table-sm">
|
||||
<tr class="border-black" style="border-bottom:1px solid #dddddd;">
|
||||
<td><strong>Subtotal</strong></td>
|
||||
<td class="text-right">
|
||||
<span t-field="o.amount_untaxed" t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
|
||||
<span t-field="o.amount_untaxed"/>
|
||||
</td>
|
||||
</tr>
|
||||
<t t-foreach="o.amount_by_group" t-as="amount_by_group">
|
||||
@@ -172,7 +160,7 @@
|
||||
<tr class="border-black">
|
||||
<td><strong>Total</strong></td>
|
||||
<td class="text-right">
|
||||
<span t-field="o.amount_total" t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
|
||||
<span t-field="o.amount_total"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -219,7 +207,7 @@
|
||||
<tr class="border-black">
|
||||
<td><strong>Amount Due</strong></td>
|
||||
<td class="text-right">
|
||||
<span t-field="o.residual" t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
|
||||
<span t-field="o.residual"/>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<t t-set="tx_ids" t-value="invoice.transaction_ids.filtered(lambda tx: tx.state in ('pending', 'authorized', 'done'))"/>
|
||||
<t t-set="pending_manual_txs" t-value="tx_ids.filtered(lambda tx: tx.state == 'pending' and tx.acquirer_id.provider in ('transfer', 'manual'))"/>
|
||||
<a t-if="invoice.state == 'open' and invoice.amount_total and invoice.type == 'out_invoice' and (pending_manual_txs or not tx_ids)"
|
||||
t-att-href="'/my/invoices/%s#portal_pay' % invoice.id" title="Pay now" aria-label="Pay now" class="btn btn-sm btn-primary" role="button">
|
||||
t-att-href="invoice.get_portal_url(anchor='portal_pay')" title="Pay now" aria-label="Pay now" class="btn btn-sm btn-primary" role="button">
|
||||
<i class="fa fa-arrow-circle-right"/><span class='d-none d-md-inline'> Pay Now</span>
|
||||
</a>
|
||||
</td>
|
||||
@@ -49,8 +49,8 @@
|
||||
<t t-call="payment.payment_tokens_list">
|
||||
<t t-set="mode" t-value="'payment'"/>
|
||||
<t t-set="partner_id" t-value="invoice.partner_id.id"/>
|
||||
<t t-set="success_url" t-value="'/my/invoices/%s?%s' % (invoice.id, keep_query())"/>
|
||||
<t t-set="error_url" t-value="'/my/invoices/%s?%s' % (invoice.id, keep_query())"/>
|
||||
<t t-set="success_url" t-value="invoice.get_portal_url()"/>
|
||||
<t t-set="error_url" t-value="invoice.get_portal_url()"/>
|
||||
<t t-set="access_token" t-value="access_token or ''"/>
|
||||
<t t-set="callback_method" t-value="''"/>
|
||||
<t t-set="form_action" t-value="'/invoice/pay/' + str(invoice.id) + '/s2s_token_tx/'"/>
|
||||
|
||||
@@ -15,10 +15,10 @@ class AccountInvoice(models.Model):
|
||||
|
||||
amount_total_words = fields.Char("Total (In Words)", compute="_compute_amount_total_words")
|
||||
|
||||
def _get_printed_report_name(self):
|
||||
def _get_report_base_filename(self):
|
||||
self.ensure_one()
|
||||
if self.company_id.country_id.code != 'IN':
|
||||
return super(AccountInvoice, self)._get_printed_report_name()
|
||||
return super(AccountInvoice, self)._get_report_base_filename()
|
||||
return self.type == 'out_invoice' and self.state == 'draft' and _('Draft %s') % (self.journal_id.name) or \
|
||||
self.type == 'out_invoice' and self.state in ('open','in_payment','paid') and '%s - %s' % (self.journal_id.name, self.number) or \
|
||||
self.type == 'out_refund' and self.state == 'draft' and _('Credit Note') or \
|
||||
|
||||
@@ -2,18 +2,20 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import math
|
||||
import re
|
||||
|
||||
from werkzeug import urls
|
||||
|
||||
from odoo import fields as odoo_fields, tools, _
|
||||
from odoo.exceptions import ValidationError, AccessError, MissingError
|
||||
from odoo.http import Controller, request, route
|
||||
from odoo.exceptions import ValidationError, AccessError, MissingError, UserError
|
||||
from odoo.http import content_disposition, Controller, request, route
|
||||
from odoo.tools import consteq
|
||||
|
||||
# --------------------------------------------------
|
||||
# Misc tools
|
||||
# --------------------------------------------------
|
||||
|
||||
|
||||
def pager(url, total, page=1, step=30, scope=5, url_args=None):
|
||||
""" Generate a dict with required value to render `website.pager` template. This method compute
|
||||
url, page range to display, ... in the pager.
|
||||
@@ -264,3 +266,23 @@ class CustomerPortal(Controller):
|
||||
values.update(get_records_pager(history, document))
|
||||
|
||||
return values
|
||||
|
||||
def _show_report(self, model, report_type, report_ref, download=False):
|
||||
if report_type not in ('html', 'pdf', 'text'):
|
||||
raise UserError("Invalid report type: %s" % report_type)
|
||||
|
||||
report_sudo = request.env.ref(report_ref).sudo()
|
||||
|
||||
if not isinstance(report_sudo, type(request.env['ir.actions.report'])):
|
||||
raise UserError("%s is not the reference of a report" % report_ref)
|
||||
|
||||
method_name = 'render_qweb_%s' % (report_type)
|
||||
report = getattr(report_sudo, method_name)([model.id], data={'report_type': report_type})[0]
|
||||
reporthttpheaders = [
|
||||
('Content-Type', 'application/pdf' if report_type == 'pdf' else 'text/html'),
|
||||
('Content-Length', len(report)),
|
||||
]
|
||||
if report_type == 'pdf' and download:
|
||||
filename = "%s.pdf" % (re.sub('\W+', '-', model._get_report_base_filename()))
|
||||
reporthttpheaders.append(('Content-Disposition', content_disposition(filename)))
|
||||
return request.make_response(report, headers=reporthttpheaders)
|
||||
|
||||
@@ -145,3 +145,25 @@ class PortalMixin(models.AbstractModel):
|
||||
"database.secret")
|
||||
token = (self.env.cr.dbname, self.access_token, pid)
|
||||
return hmac.new(secret.encode('utf-8'), repr(token).encode('utf-8'), hashlib.sha256).hexdigest()
|
||||
|
||||
@api.multi
|
||||
def get_portal_url(self, suffix=None, report_type=None, download=None, query_string=None, anchor=None):
|
||||
"""
|
||||
Get a portal url for this model, including access_token.
|
||||
The associated route must handle the flags for them to have any effect.
|
||||
- suffix: string to append to the url, before the query string
|
||||
- report_type: report_type query string, often one of: html, pdf, text
|
||||
- download: set the download query string to true
|
||||
- query_string: additional query string
|
||||
- anchor: string to append after the anchor #
|
||||
"""
|
||||
self.ensure_one()
|
||||
url = self.access_url + '%s?access_token=%s%s%s%s%s' % (
|
||||
suffix if suffix else '',
|
||||
self._portal_ensure_token(),
|
||||
'&report_type=%s' % report_type if report_type else '',
|
||||
'&download=true' if download else '',
|
||||
query_string if query_string else '',
|
||||
'#%s' % anchor if anchor else ''
|
||||
)
|
||||
return url
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
</main>
|
||||
<footer class="bg-light o_footer">
|
||||
<div class="container mt16 mb16" id="footer">
|
||||
<div class="float-right" t-ignore="true" t-if="not editable">
|
||||
<div class="float-right mb16" t-ignore="true" t-if="not editable">
|
||||
Powered by <a target="_blank" href="http://www.odoo.com?utm_source=db&utm_medium=portal">Odoo</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -73,7 +73,7 @@ class CustomerPortal(CustomerPortal):
|
||||
)
|
||||
# search the count to display, according to the pager data
|
||||
quotations = SaleOrder.search(domain, order=sort_order, limit=self._items_per_page, offset=pager['offset'])
|
||||
request.session['my_quotes_history'] = quotations.ids[:100]
|
||||
request.session['my_quotations_history'] = quotations.ids[:100]
|
||||
|
||||
values.update({
|
||||
'date': date_begin,
|
||||
@@ -139,16 +139,14 @@ class CustomerPortal(CustomerPortal):
|
||||
return request.render("sale.portal_my_orders", values)
|
||||
|
||||
@http.route(['/my/orders/<int:order_id>'], type='http', auth="public", website=True)
|
||||
def portal_order_page(self, order_id, pdf=None, access_token=None, message=False, **kw):
|
||||
def portal_order_page(self, order_id, report_type=None, access_token=None, message=False, download=False, **kw):
|
||||
try:
|
||||
order_sudo = self._document_check_access('sale.order', order_id, access_token=access_token)
|
||||
except (AccessError, MissingError):
|
||||
return request.redirect('/my')
|
||||
|
||||
if pdf:
|
||||
pdf = request.env.ref('sale.report_web_quote').sudo().with_context(set_viewport_size=True).render_qweb_pdf([order_sudo.id])[0]
|
||||
pdfhttpheaders = [('Content-Type', 'application/pdf'), ('Content-Length', len(pdf))]
|
||||
return request.make_response(pdf, headers=pdfhttpheaders)
|
||||
if report_type in ('html', 'pdf', 'text'):
|
||||
return self._show_report(model=order_sudo, report_type=report_type, report_ref='sale.report_web_quote', download=download)
|
||||
|
||||
# use sudo to allow accessing/viewing orders for public user
|
||||
# only if he knows the private token
|
||||
@@ -162,18 +160,16 @@ class CustomerPortal(CustomerPortal):
|
||||
|
||||
transaction = order_sudo.get_portal_last_transaction()
|
||||
|
||||
# TODO SEB fix breadcrumbs -> should always be visible (or at least no home alone)
|
||||
values = {
|
||||
'sale_order': order_sudo,
|
||||
'message': int(message) if message else False,
|
||||
'action': request.env.ref('sale.action_quotations').id,
|
||||
'no_breadcrumbs': request.env.user.partner_id.commercial_partner_id not in order_sudo.message_partner_ids,
|
||||
'tx_state': transaction.state if transaction else False,
|
||||
'need_payment': order_sudo.invoice_status == 'to invoice' and transaction.state in ['draft', 'cancel'],
|
||||
'token': access_token,
|
||||
'return_url': '/shop/payment/validate',
|
||||
'bootstrap_formatting': True,
|
||||
'partner_id': order_sudo.partner_id.id,
|
||||
'report_type': 'html',
|
||||
}
|
||||
|
||||
if order_sudo.has_to_be_paid() or values['need_payment']:
|
||||
@@ -189,7 +185,10 @@ class CustomerPortal(CustomerPortal):
|
||||
[('partner_id', '=', order_sudo.partner_id.id),
|
||||
('acquirer_id', 'in', [acq.id for acq in values['s2s_acquirers']])])
|
||||
|
||||
history = request.session.get('my_quotes_history', [])
|
||||
if order_sudo.state in ('draft', 'sent', 'cancel'):
|
||||
history = request.session.get('my_quotations_history', [])
|
||||
else:
|
||||
history = request.session.get('my_orders_history', [])
|
||||
values.update(get_records_pager(history, order_sudo))
|
||||
|
||||
return request.render('sale.sale_order_portal_template', values)
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
<field name="partner_id" ref="base.res_partner_4"/>
|
||||
<field name="partner_invoice_id" ref="base.res_partner_address_13"/>
|
||||
<field name="partner_shipping_id" ref="base.res_partner_address_13"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="user_id" ref="base.user_admin"/>
|
||||
<field name="pricelist_id" ref="product.list0"/>
|
||||
<field name="team_id" ref="sales_team.team_sales_department"/>
|
||||
<field name="date_order" eval="(DateTime.today() - relativedelta(months=1)).strftime('%Y-%m-%d %H:%M')"/>
|
||||
@@ -86,7 +86,7 @@
|
||||
<field name="partner_id" ref="base.res_partner_4"/>
|
||||
<field name="partner_invoice_id" ref="base.res_partner_4"/>
|
||||
<field name="partner_shipping_id" ref="base.res_partner_4"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="user_id" ref="base.user_admin"/>
|
||||
<field name="pricelist_id" ref="product.list0"/>
|
||||
<field name="team_id" ref="sales_team.team_sales_department"/>
|
||||
</record>
|
||||
@@ -113,7 +113,7 @@
|
||||
<field name="partner_id" ref="base.res_partner_3"/>
|
||||
<field name="partner_invoice_id" ref="base.res_partner_address_25"/>
|
||||
<field name="partner_shipping_id" ref="base.res_partner_address_25"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="user_id" ref="base.user_admin"/>
|
||||
<field name="pricelist_id" ref="product.list0"/>
|
||||
<field name="team_id" ref="sales_team.team_sales_department"/>
|
||||
</record>
|
||||
@@ -177,7 +177,7 @@
|
||||
<field name="partner_id" ref="base.res_partner_18"/>
|
||||
<field name="partner_invoice_id" ref="base.res_partner_18"/>
|
||||
<field name="partner_shipping_id" ref="base.res_partner_18"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="user_id" ref="base.user_admin"/>
|
||||
<field name="pricelist_id" ref="product.list0"/>
|
||||
<field name="team_id" ref="sales_team.crm_team_1"/>
|
||||
</record>
|
||||
@@ -195,7 +195,7 @@
|
||||
<field name="partner_id" ref="base.res_partner_3"/>
|
||||
<field name="partner_invoice_id" ref="base.res_partner_address_11"/>
|
||||
<field name="partner_shipping_id" ref="base.res_partner_address_11"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="user_id" ref="base.user_admin"/>
|
||||
<field name="pricelist_id" ref="product.list0"/>
|
||||
<field name="team_id" ref="sales_team.team_sales_department"/>
|
||||
</record>
|
||||
@@ -621,7 +621,7 @@ Thanks!</field>
|
||||
<field name="partner_id" ref="base.partner_demo_portal"/>
|
||||
<field name="partner_invoice_id" ref="base.partner_demo_portal"/>
|
||||
<field name="partner_shipping_id" ref="base.partner_demo_portal"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="user_id" ref="base.user_admin"/>
|
||||
<field name="pricelist_id" ref="product.list0"/>
|
||||
<field name="state">sent</field>
|
||||
<field name="team_id" ref="sales_team.team_sales_department"/>
|
||||
@@ -660,7 +660,7 @@ Thanks!</field>
|
||||
<field name="partner_id" ref="base.partner_demo_portal"/>
|
||||
<field name="partner_invoice_id" ref="base.partner_demo_portal"/>
|
||||
<field name="partner_shipping_id" ref="base.partner_demo_portal"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="user_id" ref="base.user_admin"/>
|
||||
<field name="pricelist_id" ref="product.list0"/>
|
||||
<field name="team_id" ref="sales_team.team_sales_department"/>
|
||||
<field name="date_order" eval="(DateTime.today() - relativedelta(months=1)).strftime('%Y-%m-%d %H:%M')"/>
|
||||
|
||||
@@ -179,6 +179,8 @@ class SaleOrder(models.Model):
|
||||
help="Delivery date you can promise to the customer, computed from product lead times and from the shipping policy of the order.")
|
||||
amount_undiscounted = fields.Float('Amount Before Discount', compute='_compute_amount_undiscounted', digits=0)
|
||||
|
||||
type_name = fields.Char('Type Name', compute='_compute_type_name')
|
||||
|
||||
transaction_ids = fields.Many2many('payment.transaction', 'sale_order_transaction_rel', 'sale_order_id', 'transaction_id',
|
||||
string='Transactions', copy=False, readonly=True)
|
||||
authorized_transaction_ids = fields.Many2many('payment.transaction', compute='_compute_authorized_transaction_ids',
|
||||
@@ -237,6 +239,12 @@ class SaleOrder(models.Model):
|
||||
total += line.price_subtotal + line.price_unit * ((line.discount or 0.0) / 100.0) * line.product_uom_qty # why is there a discount in a field named amount_undiscounted ??
|
||||
self.amount_undiscounted = total
|
||||
|
||||
@api.multi
|
||||
@api.depends('state')
|
||||
def _compute_type_name(self):
|
||||
for record in self:
|
||||
record.type_name = _('Quotation') if self.state in ('draft', 'sent', 'cancel') else _('Sales Order')
|
||||
|
||||
@api.multi
|
||||
def unlink(self):
|
||||
for order in self:
|
||||
@@ -818,15 +826,6 @@ class SaleOrder(models.Model):
|
||||
def payment_action_void(self):
|
||||
self.authorized_transaction_ids.s2s_void_transaction()
|
||||
|
||||
@api.multi
|
||||
def get_portal_url(self, suffix=None):
|
||||
"""
|
||||
Get a portal url for this sale order, including access_token.
|
||||
- suffix: string to append to the url, before the query string
|
||||
"""
|
||||
self.ensure_one()
|
||||
return self.access_url + '%s?access_token=%s' % (suffix if suffix else '', self._portal_ensure_token())
|
||||
|
||||
@api.multi
|
||||
def get_portal_last_transaction(self):
|
||||
self.ensure_one()
|
||||
@@ -836,6 +835,12 @@ class SaleOrder(models.Model):
|
||||
def _get_customer_lead(self, product_tmpl_id):
|
||||
return False
|
||||
|
||||
@api.multi
|
||||
def _get_report_base_filename(self):
|
||||
self.ensure_one()
|
||||
return '%s %s' % (self.type_name, self.name)
|
||||
|
||||
|
||||
class SaleOrderLine(models.Model):
|
||||
_name = 'sale.order.line'
|
||||
_description = 'Sales Order Line'
|
||||
|
||||
@@ -37,28 +37,28 @@
|
||||
<span t-field="doc.name"/>
|
||||
</h2>
|
||||
|
||||
<div class="row mt32 mb32" id="informations">
|
||||
<div t-if="doc.client_order_ref" class="col-3">
|
||||
<div class="row col mt32 mb32" id="informations">
|
||||
<div t-if="doc.client_order_ref" class="mr32">
|
||||
<strong>Your Reference:</strong>
|
||||
<p t-field="doc.client_order_ref"/>
|
||||
</div>
|
||||
<div t-if="doc.confirmation_date and doc.state not in ['draft','sent']" class="col-3">
|
||||
<div t-if="doc.confirmation_date and doc.state not in ['draft','sent']" class="mr32">
|
||||
<strong>Date Ordered:</strong>
|
||||
<p t-field="doc.confirmation_date"/>
|
||||
</div>
|
||||
<div t-if="doc.date_order and doc.state in ['draft','sent']" class="col-3">
|
||||
<div t-if="doc.date_order and doc.state in ['draft','sent']" class="mr32">
|
||||
<strong>Quotation Date:</strong>
|
||||
<p t-field="doc.date_order"/>
|
||||
</div>
|
||||
<div t-if="doc.user_id.name" class="col-3">
|
||||
<div t-if="doc.user_id.name" class="mr32">
|
||||
<strong>Salesperson:</strong>
|
||||
<p t-field="doc.user_id"/>
|
||||
</div>
|
||||
<div name="payment_term" t-if="doc.payment_term_id" class="col-3">
|
||||
<div name="payment_term" t-if="doc.payment_term_id" class="mr32">
|
||||
<strong>Payment Terms:</strong>
|
||||
<p t-field="doc.payment_term_id"/>
|
||||
</div>
|
||||
<div t-if="doc.validity_date and doc.state in ['draft', 'sent']" class="col-3">
|
||||
<div t-if="doc.validity_date and doc.state in ['draft', 'sent']" class="mr32">
|
||||
<strong>Expiration Date:</strong>
|
||||
<p t-field="doc.validity_date"/>
|
||||
</div>
|
||||
@@ -71,14 +71,14 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<t t-set="colspan" t-value="5"/>
|
||||
<th>Description</th>
|
||||
<th class="text-left">Description</th>
|
||||
<th class="text-right">Quantity</th>
|
||||
<th class="text-right">Unit Price</th>
|
||||
<th t-if="display_discount" class="text-right" groups="sale.group_discount_per_so_line">
|
||||
<span>Disc.(%)</span>
|
||||
<t t-set="colspan" t-value="colspan+1"/>
|
||||
</th>
|
||||
<th class="text-right">Taxes</th>
|
||||
<th class="text-left">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,20 +107,12 @@
|
||||
<td t-if="display_discount" class="text-right" groups="sale.group_discount_per_so_line">
|
||||
<span t-field="line.discount"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<td class="text-left">
|
||||
<span t-esc="', '.join(map(lambda x: (x.description or x.name), line.tax_id))"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span
|
||||
t-field="line.price_subtotal"
|
||||
t-options='{"widget": "monetary", "display_currency": doc.pricelist_id.currency_id}'
|
||||
groups="account.group_show_line_subtotals_tax_excluded"
|
||||
/>
|
||||
<span
|
||||
t-field="line.price_total"
|
||||
t-options='{"widget": "monetary", "display_currency": doc.pricelist_id.currency_id}'
|
||||
groups="account.group_show_line_subtotals_tax_included"
|
||||
/>
|
||||
<span t-field="line.price_subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
|
||||
<span t-field="line.price_total" groups="account.group_show_line_subtotals_tax_included"/>
|
||||
</td>
|
||||
</t>
|
||||
<t t-if="line.display_type == 'line_section'">
|
||||
@@ -140,7 +132,8 @@
|
||||
<t t-if="current_section and (line_last or doc.order_line[line_index+1].display_type == 'line_section')">
|
||||
<tr class="is-subtotal text-right">
|
||||
<td t-att-colspan="colspan">
|
||||
<strong>Subtotal:</strong> <span
|
||||
<strong class="mr16">Subtotal</strong>
|
||||
<span
|
||||
t-esc="current_subtotal"
|
||||
t-options='{"widget": "monetary", "display_currency": doc.pricelist_id.currency_id}'
|
||||
/>
|
||||
@@ -153,13 +146,12 @@
|
||||
|
||||
<div class="clearfix">
|
||||
<div class="row" name="total">
|
||||
<div class="col-4 ml-auto">
|
||||
<table class="table table-sm" style="min-width: 200px;max-width: 350px;">
|
||||
<div t-attf-class="#{'col-4' if report_type != 'html' else 'col-sm-7 col-md-5'} ml-auto">
|
||||
<table class="table table-sm">
|
||||
<tr class="border-black" style="border-bottom:1px solid #dddddd;">
|
||||
<td><strong>Subtotal</strong></td>
|
||||
<td class="text-right">
|
||||
<span t-field="doc.amount_untaxed"
|
||||
t-options='{"widget": "monetary", "display_currency": doc.pricelist_id.currency_id}'/>
|
||||
<span t-field="doc.amount_untaxed"/>
|
||||
</td>
|
||||
</tr>
|
||||
<t t-foreach="doc.amount_by_group" t-as="amount_by_group">
|
||||
@@ -188,8 +180,7 @@
|
||||
<tr class="border-black">
|
||||
<td><strong>Total</strong></td>
|
||||
<td class="text-right">
|
||||
<span t-field="doc.amount_total"
|
||||
t-options='{"widget": "monetary", "display_currency": doc.pricelist_id.currency_id}'/>
|
||||
<span t-field="doc.amount_total"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
}
|
||||
|
||||
tr.o_is_line_note {
|
||||
color: #999;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
tr.o_is_line_section {
|
||||
font-weight: bold;
|
||||
background-color: #EEE;
|
||||
background-color: #DDDDDD;
|
||||
}
|
||||
|
||||
@@ -3,18 +3,17 @@
|
||||
|
||||
<template id="portal_my_home_menu_sale" name="Portal layout : sales menu entries" inherit_id="portal.portal_layout" priority="20">
|
||||
<xpath expr="//ol[hasclass('o_portal_submenu')]" position="inside">
|
||||
<li t-if="page_name == 'quote' or sales_order and sales_order.state in ('sent', 'cancel')" class="breadcrumb-item">
|
||||
<a t-if="sales_order" t-attf-href="/my/quotes?{{ keep_query() }}">Quotations</a>
|
||||
<li t-if="page_name == 'quote' or sale_order and sale_order.state in ('sent', 'cancel')" class="breadcrumb-item">
|
||||
<a t-if="sale_order" t-attf-href="/my/quotes?{{ keep_query() }}">Quotations</a>
|
||||
<t t-else="">Quotations</t>
|
||||
</li>
|
||||
<li t-if="page_name == 'order' or sales_order and sales_order.state not in ('sent', 'cancel')" class="breadcrumb-item">
|
||||
<a t-if="sales_order" t-attf-href="/my/orders?{{ keep_query() }}">Sales Orders</a>
|
||||
<li t-if="page_name == 'order' or sale_order and sale_order.state not in ('sent', 'cancel')" class="breadcrumb-item">
|
||||
<a t-if="sale_order" t-attf-href="/my/orders?{{ keep_query() }}">Sales Orders</a>
|
||||
<t t-else="">Sales Orders</t>
|
||||
</li>
|
||||
<li t-if="sales_order" class="breadcrumb-item">
|
||||
<t t-if="sales_order.state in ('sent', 'cancel')">Quotation</t>
|
||||
<t t-else="">Order</t>
|
||||
<t t-esc="sales_order.name"/>
|
||||
<li t-if="sale_order" class="breadcrumb-item">
|
||||
<span t-field="sale_order.type_name"/>
|
||||
<t t-esc="sale_order.name"/>
|
||||
</li>
|
||||
</xpath>
|
||||
</template>
|
||||
@@ -67,7 +66,7 @@
|
||||
</t>
|
||||
</td>
|
||||
<td>
|
||||
<span t-field="quotation.amount_total" t-options='{"widget": "monetary", "display_currency": quotation.pricelist_id.currency_id}'/>
|
||||
<span t-field="quotation.amount_total"/>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
@@ -114,7 +113,7 @@
|
||||
<span class="badge badge-success d-none d-md-inline-block"><i class="fa fa-fw fa-check" role="img" aria-label="Done" title="Done"></i><span class="d-none d-md-inline"> Done</span></span>
|
||||
</t>
|
||||
</td>
|
||||
<td><span t-field="order.amount_total" t-options='{"widget": "monetary", "display_currency": order.pricelist_id.currency_id}'/></td>
|
||||
<td><span t-field="order.amount_total"/></td>
|
||||
</tr>
|
||||
</t>
|
||||
</table></div>
|
||||
@@ -131,16 +130,22 @@
|
||||
<div class="row mt16 o_portal_sale_sidebar">
|
||||
<!-- Sidebar -->
|
||||
<div class="col-lg-3 d-print-none">
|
||||
<div class="bs-sidebar">
|
||||
<!-- TODO SEB add sale order total here, like invoice for the style -->
|
||||
<div class="bs-sidebar mb16">
|
||||
<div class="text-center pt16 o_account_portal_sidebar">
|
||||
<h2 class="media-heading">
|
||||
<strong>
|
||||
<span t-field="sale_order.amount_total"/>
|
||||
</strong>
|
||||
</h2>
|
||||
</div>
|
||||
<t t-if="sale_order.team_id.team_type != 'website'">
|
||||
<div class="text-center" t-if="sale_order.state in ('draft', 'sent', 'waiting_date')" style="padding: 10px">
|
||||
<t t-set="show_hr" t-value="False"/>
|
||||
<a role="button" t-if="not sale_order.is_expired and not sale_order.signature and sale_order.has_to_be_signed()" class="btn btn-success btn-block" data-toggle="modal" data-target="#modalaccept" href="#">
|
||||
<a role="button" t-if="not sale_order.is_expired and not sale_order.signature and sale_order.has_to_be_signed()" class="btn btn-primary btn-block" data-toggle="modal" data-target="#modalaccept" href="#">
|
||||
<i class="fa fa-check"/> Accept
|
||||
<t t-set="show_hr" t-value="True"/>
|
||||
</a>
|
||||
<a role="button" t-elif="not sale_order.is_expired and sale_order.has_to_be_paid()" class="btn btn-success btn-block" data-toggle="modal" data-target="#modalaccept" href="#">
|
||||
<a role="button" t-elif="not sale_order.is_expired and sale_order.has_to_be_paid()" class="btn btn-primary btn-block" data-toggle="modal" data-target="#modalaccept" href="#">
|
||||
<i class="fa fa-check"/> <t t-if="not sale_order.signature">Accept & </t>Pay
|
||||
<t t-set="show_hr" t-value="True"/>
|
||||
</a>
|
||||
@@ -152,7 +157,7 @@
|
||||
<hr class="mb0" t-if="show_hr"/>
|
||||
</div>
|
||||
<div class="text-center" t-if="need_payment or (sale_order.state in ('sale') and sale_order.has_to_be_paid() and tx_state != 'done')" style="padding: 10px">
|
||||
<a role="button" class="btn btn-success btn-block" data-toggle="modal" data-target="#modalaccept" href="#">
|
||||
<a role="button" class="btn btn-primary btn-block" data-toggle="modal" data-target="#modalaccept" href="#">
|
||||
<i class="fa fa-check"></i> Pay Now
|
||||
</a>
|
||||
<hr class="mb0"/>
|
||||
@@ -185,23 +190,20 @@
|
||||
<hr class="mb0"/>
|
||||
<p class="text-muted mb8">Your advantage:</p>
|
||||
<t t-if="sale_order.amount_untaxed == sale_order.amount_total">
|
||||
<strong t-field="sale_order.amount_total"
|
||||
t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'/>
|
||||
<strong t-field="sale_order.amount_total"/>
|
||||
<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="text-danger"/>
|
||||
</t>
|
||||
<t t-if="sale_order.amount_untaxed != sale_order.amount_total">
|
||||
<strong t-field="sale_order.amount_untaxed"
|
||||
t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'/>
|
||||
<strong t-field="sale_order.amount_untaxed"/>
|
||||
<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="text-danger"/>
|
||||
<br />
|
||||
(<span t-field="sale_order.amount_total"
|
||||
t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'/> Incl. tax)
|
||||
(<span t-field="sale_order.amount_total"/> Incl. tax)
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
@@ -209,12 +211,21 @@
|
||||
<div class="pb8 ml16 mr16" t-if="sale_order.user_id">
|
||||
<hr class="mb0"/>
|
||||
<div><strong>Your Contact:</strong></div>
|
||||
<img class="rounded-circle mr4 float-left o_portal_contact_img" t-if="sale_order.user_id.image" t-attf-src="data:image/png;base64,#{sale_order.user_id.image}" alt="Contact"/>
|
||||
<img class="rounded-circle mr4 float-left o_portal_contact_img" t-if="not sale_order.user_id.image" src="/web/static/src/img/placeholder.png" alt="Contact"/>
|
||||
<span t-field="sale_order.user_id" t-options='{"widget": "contact", "fields": ["name", "phone"], "no_marker": True}'/>
|
||||
<a href="#discussion"><i class="fa fa-comment"></i> Contact us</a>
|
||||
<div class="d-flex h-100">
|
||||
<div class="align-self-center">
|
||||
<img class="rounded-circle mr4 float-left o_portal_contact_img" t-if="sale_order.user_id.image" t-attf-src="data:image/png;base64,#{sale_order.user_id.image}" alt="Contact"/>
|
||||
<img class="rounded-circle mr4 float-left o_portal_contact_img" t-if="not sale_order.user_id.image" src="/web/static/src/img/placeholder.png" alt="Contact"/>
|
||||
</div>
|
||||
<div class="align-self-center">
|
||||
<span t-field="sale_order.user_id" t-options='{"widget": "contact", "fields": ["name", "phone"], "no_marker": True}'/>
|
||||
<a href="#discussion"><i class="fa fa-comment"></i> Contact us</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center d-none d-md-block o_portal_brand">
|
||||
Powered by <a target="_blank" href="https://www.odoo.com?utm_source=db&utm_medium=portal">Odoo</a>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Page content -->
|
||||
<div id="quote_content" class="col-lg-9">
|
||||
@@ -258,13 +269,13 @@
|
||||
<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" t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'/>
|
||||
<b data-id="total_amount" t-field="sale_order.amount_total"/>
|
||||
<span>with payment terms: </span><b t-field="sale_order.payment_term_id"/>.
|
||||
</p>
|
||||
<t t-call="portal.portal_signature">
|
||||
<t t-set="object" t-value="sale_order"/>
|
||||
<t t-set="partner_name" t-value="sale_order.partner_id.name"/>
|
||||
<t t-set="callUrl" t-value="sale_order.get_portal_url('/accept')"/>
|
||||
<t t-set="callUrl" t-value="sale_order.get_portal_url(suffix='/accept')"/>
|
||||
<t t-set="accessToken" t-value="sale_order.access_token"/>
|
||||
</t>
|
||||
</main>
|
||||
@@ -281,7 +292,7 @@
|
||||
<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" t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'/>
|
||||
<b data-id="total_amount" t-field="sale_order.amount_total"/>
|
||||
<span>with payment terms: </span><b t-field="sale_order.payment_term_id"/>.
|
||||
</p>
|
||||
</main>
|
||||
@@ -292,8 +303,8 @@
|
||||
<t t-set="mode" t-value="'payment'"/>
|
||||
<t t-set="submit_txt" t-value="'Pay & Confirm'"/>
|
||||
<t t-set="icon_class" t-value="'fa-lock'"/>
|
||||
<t t-set="form_action" t-value="sale_order.get_portal_url('/transaction/token')"/>
|
||||
<t t-set="prepare_tx_url" t-value="sale_order.get_portal_url('/transaction/')"/>
|
||||
<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>
|
||||
@@ -332,22 +343,28 @@
|
||||
|
||||
<t t-if="sale_order.team_id.team_type != 'website'">
|
||||
|
||||
<div class="text-center d-print-none mt16 mb16" id="quotation_toolbar" t-if="sale_order.state in ('draft', 'sent', 'waiting_date')">
|
||||
<div class="row justify-content-center text-center d-print-none mt8 mb16" t-if="sale_order.state in ('draft', 'sent', 'waiting_date')">
|
||||
<t t-set="show_buttons" t-value="False"/>
|
||||
<a role="button" class="btn btn-success" data-toggle="modal" data-target="#modalaccept" t-if="not sale_order.is_expired and not sale_order.signature and sale_order.has_to_be_signed()" href="#">
|
||||
<i class="fa fa-check"></i> Accept
|
||||
<t t-set="show_buttons" t-value="True"/>
|
||||
</a>
|
||||
<a role="button" class="btn btn-success" data-toggle="modal" data-target="#modalaccept" t-elif="not sale_order.is_expired and sale_order.has_to_be_paid()" href="#">
|
||||
<i class="fa fa-check"/> <t t-if="not sale_order.signature">Accept & </t>Pay
|
||||
<t t-set="show_buttons" t-value="True"/>
|
||||
</a>
|
||||
<a role="button" class="btn btn-info" type="submit" href="#discussion" t-if="show_buttons">
|
||||
<i class="fa fa-comment"></i> Feedback
|
||||
</a>
|
||||
<a role="button" class="btn btn-danger" data-toggle="modal" data-target="#modaldecline" href="#" t-if="show_buttons">
|
||||
<i class="fa fa-times"></i> Reject
|
||||
</a>
|
||||
<div class="col-sm-auto mt8">
|
||||
<a role="button" class="btn btn-primary" data-toggle="modal" data-target="#modalaccept" t-if="not sale_order.is_expired and not sale_order.signature and sale_order.has_to_be_signed()" href="#">
|
||||
<i class="fa fa-check"></i> Accept
|
||||
<t t-set="show_buttons" t-value="True"/>
|
||||
</a>
|
||||
<a role="button" class="btn btn-primary" data-toggle="modal" data-target="#modalaccept" t-elif="not sale_order.is_expired and sale_order.has_to_be_paid()" href="#">
|
||||
<i class="fa fa-check"/> <t t-if="not sale_order.signature">Accept & </t>Pay
|
||||
<t t-set="show_buttons" t-value="True"/>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-sm-auto mt8">
|
||||
<a role="button" class="btn btn-secondary" type="submit" href="#discussion" t-if="show_buttons">
|
||||
<i class="fa fa-comment"></i> Feedback
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-sm-auto mt8">
|
||||
<a role="button" class="btn btn-danger" data-toggle="modal" data-target="#modaldecline" href="#" t-if="show_buttons">
|
||||
<i class="fa fa-times"></i> Reject
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center" t-if="need_payment or (sale_order.state in ('sale') and sale_order.has_to_be_paid() and tx_state != 'done')" style="padding: 10px">
|
||||
<a role="button" class="btn btn-primary" data-toggle="modal" data-target="#modalaccept" href="#">
|
||||
@@ -376,17 +393,18 @@
|
||||
<!-- Intro -->
|
||||
<div id="introduction" class="page-header mt16">
|
||||
<h1>
|
||||
<t t-if="sale_order.state in ('draft','sent','cancel')">Quotation</t>
|
||||
<t t-if="sale_order.state not in ('draft','sent','cancel')">Sales Order</t>
|
||||
<span t-field="sale_order.type_name"></span>
|
||||
<em t-esc="sale_order.name"/>
|
||||
<div t-ignore="true" class="float-md-right css_editable_mode_hidden d-print-none">
|
||||
<ul class="list-inline">
|
||||
<li groups="sales_team.group_sale_salesman" class="list-inline-item">
|
||||
<a role="button" t-if="sale_order.state not in ('manual')" class="btn btn-info" t-att-href="'/web#return_label=Website&model=%s&id=%s&action=%s&view_type=form' % (sale_order._name, sale_order.id, action)">Edit Quotation</a>
|
||||
<a role="button" t-if="sale_order.state in ('manual')" class=" btn btn-info" t-att-href="'/web#return_label=Website&model=%s&id=%s&action=%s&view_type=form' % (sale_order._name, sale_order.id, action)">Back to Sales Order</a>
|
||||
<a role="button" class="btn btn-info" t-att-href="'/web#return_label=Website&model=%s&id=%s&action=%s&view_type=form' % (sale_order._name, sale_order.id, sale_order.env.ref('sale.action_quotations').id)">
|
||||
<t t-if="sale_order.state in ('draft', 'sent', 'cancel')">Edit Quotation</t>
|
||||
<t t-else="">Edit Sales Order</t>
|
||||
</a>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<a role="button" class="btn btn-info" target="_blank" t-att-href="sale_order.get_portal_url() + '&pdf=True'">Print</a>
|
||||
<a role="button" class="btn btn-info" target="_blank" t-att-href="sale_order.get_portal_url(report_type='pdf')">Print</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -434,7 +452,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<t t-foreach="invoices" t-as="i">
|
||||
<t t-set="report_url" t-value="'/my/invoices/pdf/%s?%s' % (i.id, 'access_token=%s' % i.access_token if access_token else '')"/>
|
||||
<t t-set="report_url" t-value="i.get_portal_url(report_type='pdf', download=True)"/>
|
||||
<div>
|
||||
<a t-att-href="report_url"><span class="fa fa-download" role="img" aria-label="Download" title="Download"/></a>
|
||||
<a t-att-href="report_url"><span t-esc="i.number"/></a>
|
||||
@@ -452,168 +470,150 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_structure"/>
|
||||
<!-- Pricing -->
|
||||
<!-- Signature -->
|
||||
<section id="details">
|
||||
|
||||
<section id="details" style="page-break-inside: auto;">
|
||||
<h1 class="page-header" id="details">Details</h1>
|
||||
|
||||
<t t-set="display_taxamount" t-value="any([line.tax_id for line in sale_order.order_line])"/>
|
||||
<t t-set="display_discount" t-value="True in [line.discount > 0 for line in sale_order.order_line]"/>
|
||||
|
||||
<div style="overflow-x:auto;">
|
||||
<table class="table" id="sales_order_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<t t-set="colspan" t-value="6"/>
|
||||
<th></th>
|
||||
<th>Products</th>
|
||||
<th>Quantity</th>
|
||||
<th t-if="display_taxamount">
|
||||
<span>Taxes</span>
|
||||
<t t-set="colspan" t-value="colspan+1"/>
|
||||
</th>
|
||||
<th t-if="display_discount">
|
||||
<span>Discount</span>
|
||||
<t t-set="colspan" t-value="colspan+1"/>
|
||||
</th>
|
||||
<th class="text-right">Unit Price</th>
|
||||
<th class="text-right" >
|
||||
<span groups="account.group_show_line_subtotals_tax_excluded">Amount</span>
|
||||
<span groups="account.group_show_line_subtotals_tax_included">Total Price</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="sale_tbody">
|
||||
<table class="table" id="sales_order_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<t t-set="colspan" t-value="7"/>
|
||||
<th class="d-none d-xl-table-cell"></th>
|
||||
<th class="text-left">Products</th>
|
||||
<th class="text-left">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-right">
|
||||
<span>Discount</span>
|
||||
<t t-set="colspan" t-value="colspan+1"/>
|
||||
</th>
|
||||
<th t-attf-class="text-left {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}">Taxes</th>
|
||||
<th class="text-right" >
|
||||
<span groups="account.group_show_line_subtotals_tax_excluded">Amount</span>
|
||||
<span groups="account.group_show_line_subtotals_tax_included">Total Price</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="sale_tbody">
|
||||
|
||||
<t t-set="current_subtotal" t-value="0"/>
|
||||
<t t-set="current_subtotal" t-value="0"/>
|
||||
|
||||
<t t-foreach="sale_order.order_line" t-as="line">
|
||||
<t t-foreach="sale_order.order_line" t-as="line">
|
||||
|
||||
<t t-set="current_subtotal" t-value="current_subtotal + line.price_subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
|
||||
<t t-set="current_subtotal" t-value="current_subtotal + line.price_total" groups="account.group_show_line_subtotals_tax_included"/>
|
||||
<t t-set="current_subtotal" t-value="current_subtotal + line.price_subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
|
||||
<t t-set="current_subtotal" t-value="current_subtotal + line.price_total" groups="account.group_show_line_subtotals_tax_included"/>
|
||||
|
||||
<tr t-attf-class="{{ 'o_is_' + line.display_type if line.display_type else '' }}">
|
||||
<t t-if="not line.display_type">
|
||||
<td class="text-center">
|
||||
<div class="d-none d-xl-block">
|
||||
<span t-field="line.product_image" t-options="{'widget': 'image', 'class': 'rounded', 'style':'width: 48px;height:48px'}" />
|
||||
</div>
|
||||
</td>
|
||||
<td id="product_name"><span t-field="line.name"/></td>
|
||||
<td>
|
||||
<div id="quote_qty">
|
||||
<span t-field="line.product_uom_qty"/>
|
||||
<span t-field="line.product_uom" groups="uom.group_uom"/>
|
||||
</div>
|
||||
</td>
|
||||
<td t-if="display_taxamount">
|
||||
<span t-esc="', '.join(map(lambda x: (x.description or x.name), line.tax_id))"/>
|
||||
</td>
|
||||
<td t-if="display_discount">
|
||||
<strong t-if="line.discount" class="text-info">
|
||||
<t t-esc="((line.discount % 1) and '%s' or '%d') % line.discount"/>% discount
|
||||
</strong>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<div
|
||||
t-field="line.price_unit"
|
||||
t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'
|
||||
t-att-style="line.discount and 'text-decoration: line-through' or None"
|
||||
t-att-class="(line.discount and 'text-danger' or '') + ' text-right'"
|
||||
/>
|
||||
<div t-if="line.discount">
|
||||
<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 class="text-right">
|
||||
<span
|
||||
t-field="line.price_subtotal"
|
||||
t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'
|
||||
groups="account.group_show_line_subtotals_tax_excluded"
|
||||
/>
|
||||
<span
|
||||
t-field="line.price_total"
|
||||
t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'
|
||||
groups="account.group_show_line_subtotals_tax_included"
|
||||
/>
|
||||
</td>
|
||||
</t>
|
||||
<t t-if="line.display_type == 'line_section'">
|
||||
<td t-att-colspan="colspan">
|
||||
<span t-field="line.name"/>
|
||||
</td>
|
||||
<t t-set="current_section" t-value="line"/>
|
||||
<t t-set="current_subtotal" t-value="0"/>
|
||||
</t>
|
||||
<t t-if="line.display_type == 'line_note'">
|
||||
<td t-att-colspan="colspan">
|
||||
<span t-field="line.name"/>
|
||||
</td>
|
||||
</t>
|
||||
</tr>
|
||||
|
||||
<t t-if="current_section and (line_last or sale_order.order_line[line_index+1].display_type == 'line_section')">
|
||||
<tr class="is-subtotal text-right">
|
||||
<td t-att-colspan="colspan">
|
||||
<strong>Subtotal:</strong>
|
||||
<span
|
||||
t-esc="current_subtotal"
|
||||
t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
</t>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="clearfix">
|
||||
<div id="total" class="row" name="total">
|
||||
<div class="col-8 col-sm-6 col-md-4 ml-auto">
|
||||
<table class="table table-sm">
|
||||
<tr class="border-black" style="border-bottom:1px solid #dddddd;">
|
||||
<td><strong>Subtotal</strong></td>
|
||||
<tr t-attf-class="{{ 'o_is_' + line.display_type if line.display_type else '' }}">
|
||||
<t t-if="not line.display_type">
|
||||
<td class="text-center d-none d-xl-table-cell">
|
||||
<div>
|
||||
<span t-field="line.product_image" t-options="{'widget': 'image', 'class': 'rounded', 'style':'width: 48px;height:48px'}" />
|
||||
</div>
|
||||
</td>
|
||||
<td id="product_name"><span t-field="line.name"/></td>
|
||||
<td>
|
||||
<div id="quote_qty">
|
||||
<span t-field="line.product_uom_qty"/>
|
||||
<span t-field="line.product_uom" groups="uom.group_uom"/>
|
||||
</div>
|
||||
</td>
|
||||
<td t-attf-class="text-right {{ 'd-none d-sm-table-cell' if report_type == 'html' else '' }}">
|
||||
<div
|
||||
t-field="line.price_unit"
|
||||
t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'
|
||||
t-att-style="line.discount and 'text-decoration: line-through' or None"
|
||||
t-att-class="(line.discount and 'text-danger' or '') + ' text-right'"
|
||||
/>
|
||||
<div t-if="line.discount">
|
||||
<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">
|
||||
<strong t-if="line.discount" class="text-info text-right">
|
||||
<t t-esc="((line.discount % 1) and '%s' or '%d') % line.discount"/>% discount
|
||||
</strong>
|
||||
</td>
|
||||
<td t-attf-class="text-left {{ 'd-none d-md-table-cell' if report_type == 'html' else '' }}">
|
||||
<span t-esc="', '.join(map(lambda x: (x.description or x.name), line.tax_id))"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-field="line.price_subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
|
||||
<span t-field="line.price_total" groups="account.group_show_line_subtotals_tax_included"/>
|
||||
</td>
|
||||
</t>
|
||||
<t t-if="line.display_type == 'line_section'">
|
||||
<td t-att-colspan="colspan">
|
||||
<span t-field="line.name"/>
|
||||
</td>
|
||||
<t t-set="current_section" t-value="line"/>
|
||||
<t t-set="current_subtotal" t-value="0"/>
|
||||
</t>
|
||||
<t t-if="line.display_type == 'line_note'">
|
||||
<td t-att-colspan="colspan">
|
||||
<span t-field="line.name"/>
|
||||
</td>
|
||||
</t>
|
||||
</tr>
|
||||
|
||||
<t t-if="current_section and (line_last or sale_order.order_line[line_index+1].display_type == 'line_section')">
|
||||
<tr class="is-subtotal text-right">
|
||||
<td t-att-colspan="colspan">
|
||||
<strong class="mr16">Subtotal</strong>
|
||||
<span
|
||||
data-id="total_amount"
|
||||
t-field="sale_order.amount_untaxed"
|
||||
t-options='{"widget": "monetary","display_currency": sale_order.pricelist_id.currency_id}'
|
||||
t-esc="current_subtotal"
|
||||
t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<t t-foreach="sale_order.amount_by_group" t-as="amount_by_group">
|
||||
<tr style="border-bottom:1px solid #dddddd;">
|
||||
<t t-if="amount_by_group[3] == 1 and sale_order.amount_untaxed == amount_by_group[2]">
|
||||
<td>
|
||||
<span t-esc="amount_by_group[0]"/>
|
||||
<span>&nbsp;<span>on</span>&nbsp;<t t-esc="amount_by_group[2]" t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'/></span>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="amount_by_group[1]"
|
||||
t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'/>
|
||||
</td>
|
||||
</t>
|
||||
<t t-else ="">
|
||||
<td>
|
||||
<span t-esc="amount_by_group[0]"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="amount_by_group[1]"
|
||||
t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'/>
|
||||
</td>
|
||||
</t>
|
||||
</tr>
|
||||
</t>
|
||||
<tr class="border-black">
|
||||
<td><strong>Total</strong></td>
|
||||
<td class="text-right">
|
||||
<span data-id="total_amount" t-field="sale_order.amount_total" t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'/>
|
||||
</td>
|
||||
</t>
|
||||
</t>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div id="total" class="row" name="total" style="page-break-inside: avoid;">
|
||||
<div t-attf-class="#{'col-4' if report_type != 'html' else 'col-sm-7 col-md-5'} ml-auto">
|
||||
<table class="table table-sm">
|
||||
<tr class="border-black" style="border-bottom:1px solid #dddddd;">
|
||||
<td><strong>Subtotal</strong></td>
|
||||
<td class="text-right">
|
||||
<span
|
||||
data-id="total_amount"
|
||||
t-field="sale_order.amount_untaxed"
|
||||
t-options='{"widget": "monetary","display_currency": sale_order.pricelist_id.currency_id}'
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<t t-foreach="sale_order.amount_by_group" t-as="amount_by_group">
|
||||
<tr style="border-bottom:1px solid #dddddd;">
|
||||
<t t-if="amount_by_group[3] == 1 and sale_order.amount_untaxed == amount_by_group[2]">
|
||||
<td>
|
||||
<span t-esc="amount_by_group[0]"/>
|
||||
<span>&nbsp;<span>on</span>&nbsp;<t t-esc="amount_by_group[2]" t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'/></span>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="amount_by_group[1]"
|
||||
t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'/>
|
||||
</td>
|
||||
</t>
|
||||
<t t-else ="">
|
||||
<td>
|
||||
<span t-esc="amount_by_group[0]"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="amount_by_group[1]"
|
||||
t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'/>
|
||||
</td>
|
||||
</t>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</t>
|
||||
<tr class="border-black">
|
||||
<td><strong>Total</strong></td>
|
||||
<td class="text-right">
|
||||
<span data-id="total_amount" t-field="sale_order.amount_total" t-options='{"widget": "monetary", "display_currency": sale_order.pricelist_id.currency_id}'/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -54,7 +54,7 @@ class CustomerPortal(CustomerPortal):
|
||||
return request.redirect('/my')
|
||||
|
||||
if order_sudo.state != 'sent':
|
||||
return request.redirect(order_sudo.get_portal_url() + "&message=4")
|
||||
return request.redirect(order_sudo.get_portal_url(query_string="&message=4"))
|
||||
order_sudo.action_cancel()
|
||||
message = post.get('decline_message')
|
||||
if message:
|
||||
@@ -95,7 +95,7 @@ class CustomerPortal(CustomerPortal):
|
||||
|
||||
option_sudo.add_option_to_order()
|
||||
|
||||
return request.redirect(option_sudo.order_id.get_portal_url() + "#details")
|
||||
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)
|
||||
|
||||
@@ -163,7 +163,7 @@ class SaleOrder(models.Model):
|
||||
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() + '&%s' % auth_param
|
||||
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):
|
||||
|
||||
@@ -1,25 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<template id="report_saleorder_validity_date" inherit_id="sale.report_saleorder_document">
|
||||
<template id="report_saleorder_document_inherit_sale_management" inherit_id="sale.report_saleorder_document">
|
||||
<xpath expr="//div[hasclass('page')]/p[@id='fiscal_position_remark']" position="after">
|
||||
<div t-if="doc.sale_order_option_ids and doc.state in ['draft', 'sent']">
|
||||
<h2>
|
||||
<h4>
|
||||
<span>Optional Products</span>
|
||||
</h2>
|
||||
</h4>
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Product</th>
|
||||
<th>Description</th>
|
||||
<th groups="sale.group_discount_per_so_line"></th>
|
||||
<th class="text-left">Description</th>
|
||||
<th groups="sale.group_discount_per_so_line" class="text-left"></th>
|
||||
<th class="text-right">Unit Price</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="sale_tbody">
|
||||
<tr t-foreach="doc.sale_order_option_ids" t-as="option">
|
||||
<td>
|
||||
<span t-field="option.product_id.name"/>
|
||||
</td>
|
||||
<td>
|
||||
<span t-field="option.name"/>
|
||||
</td>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
<xpath expr="//section[@id='details']//t[@t-if='not line.display_type']" position="inside">
|
||||
<td class="text-center" t-if="display_remove">
|
||||
<a t-att-href="sale_order.get_portal_url('/update_line/%s' % line.id) + '&unlink=True'" class="mb8 js_update_line_json d-print-none" t-if="line.sale_order_option_ids" aria-label="Remove" title="Remove">
|
||||
<a t-att-href="sale_order.get_portal_url(suffix='/update_line/%s' % line.id, query_string='&unlink=True')" class="mb8 js_update_line_json d-print-none" t-if="line.sale_order_option_ids" aria-label="Remove" title="Remove">
|
||||
<span class="fa fa-trash-o"></span>
|
||||
</a>
|
||||
</td>
|
||||
@@ -31,16 +31,16 @@
|
||||
<t t-if="any([(not option.line_id) for option in sale_order.sale_order_option_ids])">
|
||||
<section>
|
||||
<h1 class="page-header">Optional Products</h1>
|
||||
</section>
|
||||
<section>
|
||||
<div style="overflow-x:auto;">
|
||||
<t t-set="display_discount" t-value="True in [option.discount for option in sale_order.sale_order_option_ids]"/>
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Product</th>
|
||||
<th></th>
|
||||
<th class="text-left">Product</th>
|
||||
<th t-if="display_discount" class="text-right">
|
||||
<span>Discount</span>
|
||||
</th>
|
||||
<th class="text-right">Price</th>
|
||||
<th t-if="sale_order.state in ['draft', 'sent']"></th>
|
||||
<th t-if="sale_order.state in ['draft', 'sent'] and report_type == 'html'"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -49,8 +49,8 @@
|
||||
<td>
|
||||
<div t-field="option.name"/>
|
||||
</td>
|
||||
<td>
|
||||
<strong t-if="option.discount" class="text-info">
|
||||
<td t-if="display_discount">
|
||||
<strong t-if="option.discount" class="text-info text-right">
|
||||
<t t-esc="((option.discount % 1) and '%s' or '%d') % option.discount"/>% discount
|
||||
</strong>
|
||||
</td>
|
||||
@@ -65,8 +65,8 @@
|
||||
</div>
|
||||
</strong>
|
||||
</td>
|
||||
<td class="text-center" t-if="sale_order.state in ['draft', 'sent']">
|
||||
<a t-att-href="sale_order.get_portal_url('/add_option/%s' % option.id)" class="mb8 d-print-none" aria-label="Add to cart" title="Add to cart">
|
||||
<td class="text-center" t-if="sale_order.state in ['draft', 'sent'] and report_type == 'html'">
|
||||
<a t-att-href="sale_order.get_portal_url(suffix='/add_option/%s' % option.id)" class="mb8 d-print-none" aria-label="Add to cart" title="Add to cart">
|
||||
<span class="fa fa-shopping-cart"/>
|
||||
</a>
|
||||
</td>
|
||||
@@ -74,7 +74,6 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</t>
|
||||
</xpath>
|
||||
@@ -85,7 +84,7 @@
|
||||
|
||||
<div class="input-group-prepend d-print-none">
|
||||
<span class="input-group-text">
|
||||
<a t-att-href="sale_order.get_portal_url('/update_line/%s' % line.id) + '&remove=True'" class="mb8 js_update_line_json" aria-label="Remove one" title="Remove one">
|
||||
<a t-att-href="sale_order.get_portal_url(suffix='/update_line/%s' % line.id, query_string='&remove=True')" class="mb8 js_update_line_json" aria-label="Remove one" title="Remove one">
|
||||
<span class="fa fa-minus"/>
|
||||
</a>
|
||||
</span>
|
||||
@@ -94,7 +93,7 @@
|
||||
<input type="text" class="js_quantity form-control" t-att-data-id="line.id" t-att-value="line.product_uom_qty"/>
|
||||
<div class="input-group-append d-print-none">
|
||||
<span class="input-group-text">
|
||||
<a t-att-href="sale_order.get_portal_url('/update_line/%s' % line.id)" class="mb8 js_update_line_json" aria-label="Add one" title="Add one">
|
||||
<a t-att-href="sale_order.get_portal_url(suffix='/update_line/%s' % line.id)" class="mb8 js_update_line_json" aria-label="Add one" title="Add one">
|
||||
<span class="fa fa-plus"/>
|
||||
</a>
|
||||
</span>
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
tbody {
|
||||
color: gray('700');
|
||||
tr:nth-child(odd) {
|
||||
background-color: rgba(184, 171, 180, 0.2);
|
||||
background-color: rgba(220, 205, 216, 0.2);
|
||||
}
|
||||
tr td {
|
||||
border-top: none;
|
||||
|
||||
@@ -71,8 +71,4 @@
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.table > tbody > tr.o_is_line_section td {
|
||||
border-color: black;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,12 +18,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.table {
|
||||
td, th {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
thead {
|
||||
color: $o-main-text-color;
|
||||
border-bottom: 1px solid #cacaca;
|
||||
@@ -118,18 +112,6 @@
|
||||
border-top: none; // Override bootstrap for grouped list views
|
||||
}
|
||||
|
||||
tbody {
|
||||
tr:focus-within {
|
||||
background-color: $o-form-lightsecondary;
|
||||
}
|
||||
tr{
|
||||
.focussed {
|
||||
background-color: $o-form-lightsecondary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Values styling
|
||||
.o_list_number {
|
||||
text-align: right;
|
||||
|
||||
@@ -52,12 +52,10 @@ body {
|
||||
.row.o_is_line_section,
|
||||
.table > tbody > tr.o_is_line_section td {
|
||||
font-weight: bold;
|
||||
background-color: #EEE;
|
||||
padding: 8px;
|
||||
border: 1px solid #BBB;
|
||||
background-color: #DDDDDD;
|
||||
}
|
||||
|
||||
.row.o_is_line_note,
|
||||
tr.o_is_line_note {
|
||||
color: #999;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@
|
||||
<!-- External layouts styles -->
|
||||
|
||||
<template id="address_layout">
|
||||
<t t-set="colclass" t-value="'col-5 offset-7'"/>
|
||||
<t t-set="colclass" t-value="('col-md-5' if report_type == 'html' else 'col-5') + ' ml-auto'"/>
|
||||
<t t-if="address">
|
||||
<div class="row">
|
||||
<t t-if="information_block">
|
||||
@@ -192,7 +192,7 @@
|
||||
<li t-if="company.vat" class="list-inline-item"><i class="fa fa-building-o" role="img" aria-label="Fiscal number"/><t t-esc="company.country_id.vat_label or 'Tax ID'"/>: <span t-field="company.vat"/></li>
|
||||
</ul>
|
||||
<div t-field="company.report_footer"/>
|
||||
<div t-if="not type_html" class="text-muted">
|
||||
<div t-if="report_type == 'pdf'" class="text-muted">
|
||||
Page:
|
||||
<span class="page"/>
|
||||
of
|
||||
@@ -232,7 +232,7 @@
|
||||
<li t-if="company.vat" class="list-inline-item"><t t-esc="company.country_id.vat_label or 'Tax ID'"/>: <span t-field="company.vat"/></li>
|
||||
</ul>
|
||||
<div t-field="company.report_footer"/>
|
||||
<div t-if="not type_html">
|
||||
<div t-if="report_type == 'pdf'">
|
||||
Page: <span class="page"/> / <span class="topage"/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -275,7 +275,7 @@
|
||||
<h4 class="mt0 mb0 text-uppercase" t-field="company.report_header"/>
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<ul t-if="not type_html" class="list-inline pagenumber float-right text-center">
|
||||
<ul t-if="report_type != 'html'" class="list-inline pagenumber float-right text-center">
|
||||
<li class="list-inline-item"><strong><span class="page"/></strong></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -298,9 +298,9 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-6" name="company_address">
|
||||
<span t-field="company.partner_id"
|
||||
<div t-field="company.partner_id"
|
||||
t-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": true}'
|
||||
style="border-bottom: 1px solid black; display:inline-block;"/>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -323,7 +323,7 @@
|
||||
<span t-field="company.report_footer"/>
|
||||
</div>
|
||||
|
||||
<div t-if="not type_html" class="text-muted">
|
||||
<div t-if="report_type == 'pdf'" class="text-muted">
|
||||
Page: <span class="page"/> / <span class="topage"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -22,12 +22,13 @@
|
||||
</t>
|
||||
<div itemprop="address" itemscope="itemscope" itemtype="http://schema.org/PostalAddress">
|
||||
<div t-if="address and 'address' in fields">
|
||||
<i t-if="not options.get('no_marker')" class='fa fa-map-marker fa-fw' role="img" aria-label="Address" title="Address"/> <span itemprop="streetAddress" t-raw="address.replace('\n', options.get('no_tag_br') and ', ' or ('<br/>%s' % ('' if options.get('no_marker') else '&nbsp; &nbsp; ')))"/>
|
||||
<i t-if="not options.get('no_marker')" class='fa fa-map-marker fa-fw' role="img" aria-label="Address" title="Address"/>
|
||||
<span itemprop="streetAddress" t-raw="address.replace('\n', options.get('no_tag_br') and ', ' or ('<br/>%s' % ('' if options.get('no_marker') else '&nbsp; &nbsp; ')))"/>
|
||||
</div>
|
||||
<div t-if="city and 'city' in fields">
|
||||
<i t-if="not options.get('no_marker')" class='fa fa-map-marker fa-fw' role="img" aria-label="Address" title="Address"/>
|
||||
<span itemprop="addressLocality" t-raw="city"/>,
|
||||
<span itemprop="addressCountry" t-raw="country_id"/>
|
||||
<span itemprop="addressLocality" t-esc="city"/>,
|
||||
<span itemprop="addressCountry" t-esc="country_id"/>
|
||||
</div>
|
||||
<div t-if="phone and 'phone' in fields"><i t-if="not options.get('no_marker') or options.get('phone_icons')" class='fa fa-phone fa-fw' role="img" aria-label="Phone" title="Phone"/> <span itemprop="telephone" t-esc="phone"/></div>
|
||||
<div t-if="mobile and 'mobile' in fields"><i t-if="not options.get('no_marker') or options.get('phone_icons')" class='fa fa-mobile fa-fw' role="img" aria-label="Mobile" title="Mobile"/> <span itemprop="telephone" t-esc="mobile"/></div>
|
||||
|
||||
Reference in New Issue
Block a user