[IMP] various: improve section and add note on sale order and invoice
The goal is to be able to more easily create section and notes on sale order, invoice, and quote. ================================ Problems of the existing feature ================================ The problematic part in the existing feature was that the same section had to be defined again and again for every line, instead of being defined only once where it needed to be. There was also no easy way to move them. Those concerns are solved here. Moreover, due to the complexity of the feature, it was hidden by default, requiring a setting and multiple overrides. The new feature is simple enough that it will always be present. ================== New implementation ================== Sections are lines solely used for display purpose, there is no business logic attached to them. They will show as a nice header at the top of the section, and a subtotal at the bottom of the section. We also take the opportunity to add notes, which are just like sections except they are displayed differently and don't include a subtotal. ========= Reasoning ========= We decided to implement it this way, as opposed to creating a proper section model, because it creates less changes in the code. Creating a intermediary section model between the sale order/invoice/quote and their lines would have required to rewrite the entirety of the code associated to them. It would also have required to create a new JS widget to handle on the same view a double-level One2many (or a One2many + group by). The current solution is much easier because it just requires adding 2 buttons, 1 simple field on the corresponding models which even has a NULL default value, and making some business checks (don't apply business to section/note). We tried our best to impact as less as possible the existing models. Notably we didn't want to add any extra field if this was avoidable. This also makes the migration easier. No matter the implementation choice the views/reports had to be updated to display the sections and notes correctly, which has been done. =========== Limitations =========== The current solution has several limitations: * since the sections don't have business logic, it's not possible to make any reporting based on them * any business related to sections has to be computed on the fly by browsing in order all of the lines, notably the subtotal has to be computed on the fly at every display - when sections are moved (drag&drop) on the back-end, the following lines that are part of the section are not moving with the section =========== Technically =========== The commit will: * update the models to add the display_type field (along with related sql constraints to guarantee integrity) * add new buttons at the bottom of the lists to allow creating section and note, along with the existing add a line/product button * add some business checks to not apply business to section/note (product_id and account_id became non-required fields) !!! The most likely cause of potential bugs introduced in this commit would be to have forgotten to add those checks on some places where they would be needed. This is tedious to check but fortunately easy to fix. * update the views to display sections and notes correctly, also made various improvements/fixes to those views (notably to apply B2B/B2C setting more consistently) !!! This is the second likely cause of potential bugs: if the updated views are inherited in a way that is not compatible with the changes. Or if we forgot to apply the changes to some other views. Again this is tedious to check but fortunately easy to fix. Task #53996 PR #25209
This commit is contained in:
committed by
Sébastien Theys
parent
3ec6294739
commit
762adedece
@@ -1007,6 +1007,8 @@ class AccountInvoice(models.Model):
|
||||
def get_taxes_values(self):
|
||||
tax_grouped = {}
|
||||
for line in self.invoice_line_ids:
|
||||
if not line.account_id:
|
||||
continue
|
||||
price_unit = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
|
||||
taxes = line.invoice_line_tax_ids.compute_all(price_unit, self.currency_id, line.quantity, line.product_id, self.partner_id)['taxes']
|
||||
for tax in taxes:
|
||||
@@ -1089,6 +1091,8 @@ class AccountInvoice(models.Model):
|
||||
def invoice_line_move_line_get(self):
|
||||
res = []
|
||||
for line in self.invoice_line_ids:
|
||||
if not line.account_id:
|
||||
continue
|
||||
if line.quantity==0:
|
||||
continue
|
||||
tax_ids = []
|
||||
@@ -1193,8 +1197,8 @@ class AccountInvoice(models.Model):
|
||||
for inv in self:
|
||||
if not inv.journal_id.sequence_id:
|
||||
raise UserError(_('Please define sequence on the journal related to this invoice.'))
|
||||
if not inv.invoice_line_ids:
|
||||
raise UserError(_('Please create some invoice lines.'))
|
||||
if not inv.invoice_line_ids.filtered(lambda line: line.account_id):
|
||||
raise UserError(_('Please add at least one invoice line.'))
|
||||
if inv.move_id:
|
||||
continue
|
||||
|
||||
@@ -1575,8 +1579,7 @@ class AccountInvoiceLine(models.Model):
|
||||
product_id = fields.Many2one('product.product', string='Product',
|
||||
ondelete='restrict', index=True)
|
||||
product_image = fields.Binary('Product Image', related="product_id.image", store=False)
|
||||
account_id = fields.Many2one('account.account', string='Account',
|
||||
required=True, domain=[('deprecated', '=', False)],
|
||||
account_id = fields.Many2one('account.account', string='Account', domain=[('deprecated', '=', False)],
|
||||
default=_default_account,
|
||||
help="The income or expense account related to the selected product.")
|
||||
price_unit = fields.Float(string='Unit Price', required=True, digits=dp.get_precision('Product Price'))
|
||||
@@ -1605,6 +1608,10 @@ class AccountInvoiceLine(models.Model):
|
||||
company_currency_id = fields.Many2one('res.currency', related='invoice_id.company_currency_id', readonly=True, related_sudo=False)
|
||||
is_rounding_line = fields.Boolean(string='Rounding Line', help='Is a rounding line in case of cash rounding.')
|
||||
|
||||
display_type = fields.Selection([
|
||||
('line_section', "Section"),
|
||||
('line_note', "Note")], default=False, help="Technical field for UX purpose.")
|
||||
|
||||
@api.model
|
||||
def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False):
|
||||
res = super(AccountInvoiceLine, self).fields_view_get(
|
||||
@@ -1778,6 +1785,29 @@ class AccountInvoiceLine(models.Model):
|
||||
}
|
||||
return data
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
if vals.get('display_type', self.default_get(['display_type'])['display_type']):
|
||||
vals.update(price_unit=0, account_id=False, quantity=0)
|
||||
|
||||
return super(AccountInvoiceLine, self).create(vals)
|
||||
|
||||
@api.multi
|
||||
def write(self, values):
|
||||
if 'display_type' in values and self.filtered(lambda line: line.display_type != values.get('display_type')):
|
||||
raise UserError("You cannot change the type of an invoice line. Instead you should delete the current line and create a new line of the proper type.")
|
||||
return super(AccountInvoiceLine, self).write(values)
|
||||
|
||||
_sql_constraints = [
|
||||
('accountable_required_fields',
|
||||
"CHECK(display_type IS NOT NULL OR account_id IS NOT NULL)",
|
||||
"Missing required account on accountable invoice line."),
|
||||
|
||||
('non_accountable_fields_null',
|
||||
"CHECK(display_type IS NULL OR (price_unit = 0 AND account_id IS NULL and quantity = 0))",
|
||||
"Forbidden unit price, account and quantity on non-accountable invoice line"),
|
||||
]
|
||||
|
||||
|
||||
class AccountInvoiceTax(models.Model):
|
||||
_name = "account.invoice.tax"
|
||||
|
||||
@@ -27,6 +27,7 @@ class AccountInvoiceReport(models.Model):
|
||||
record.user_currency_price_average = base_currency_id._convert(record.price_average, user_currency_id, company, date)
|
||||
record.user_currency_residual = base_currency_id._convert(record.residual, user_currency_id, company, date)
|
||||
|
||||
number = fields.Char('Invoice Number', readonly=True)
|
||||
date = fields.Date(readonly=True)
|
||||
product_id = fields.Many2one('product.product', string='Product', readonly=True)
|
||||
product_qty = fields.Float(string='Product Quantity', readonly=True)
|
||||
@@ -73,7 +74,7 @@ class AccountInvoiceReport(models.Model):
|
||||
'account.invoice': [
|
||||
'account_id', 'amount_total_company_signed', 'commercial_partner_id', 'company_id',
|
||||
'currency_id', 'date_due', 'date_invoice', 'fiscal_position_id',
|
||||
'journal_id', 'partner_bank_id', 'partner_id', 'payment_term_id',
|
||||
'journal_id', 'number', 'partner_bank_id', 'partner_id', 'payment_term_id',
|
||||
'residual', 'state', 'type', 'user_id',
|
||||
],
|
||||
'account.invoice.line': [
|
||||
@@ -89,7 +90,7 @@ class AccountInvoiceReport(models.Model):
|
||||
|
||||
def _select(self):
|
||||
select_str = """
|
||||
SELECT sub.id, sub.date, sub.product_id, sub.partner_id, sub.country_id, sub.account_analytic_id,
|
||||
SELECT sub.id, sub.number, sub.date, sub.product_id, sub.partner_id, sub.country_id, sub.account_analytic_id,
|
||||
sub.payment_term_id, sub.uom_name, sub.currency_id, sub.journal_id,
|
||||
sub.fiscal_position_id, sub.user_id, sub.company_id, sub.nbr, sub.type, sub.state,
|
||||
sub.categ_id, sub.date_due, sub.account_id, sub.account_line_id, sub.partner_bank_id,
|
||||
@@ -102,6 +103,7 @@ class AccountInvoiceReport(models.Model):
|
||||
select_str = """
|
||||
SELECT ail.id AS id,
|
||||
ai.date_invoice AS date,
|
||||
ai.number as number,
|
||||
ail.product_id, ai.partner_id, ai.payment_term_id, ail.account_analytic_id,
|
||||
u2.name AS uom_name,
|
||||
ai.currency_id, ai.journal_id, ai.fiscal_position_id, ai.user_id, ai.company_id,
|
||||
@@ -161,7 +163,7 @@ class AccountInvoiceReport(models.Model):
|
||||
WITH currency_rate AS (%s)
|
||||
%s
|
||||
FROM (
|
||||
%s %s %s
|
||||
%s %s WHERE ail.account_id IS NOT NULL %s
|
||||
) AS sub
|
||||
LEFT JOIN currency_rate cr ON
|
||||
(cr.currency_id = sub.currency_id AND
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
|
||||
odoo.define('account.section_and_note_backend', function (require) {
|
||||
// The goal of this file is to contain JS hacks related to allowing
|
||||
// section and note on sale order and invoice.
|
||||
|
||||
"use strict";
|
||||
var ListRenderer = require('web.ListRenderer');
|
||||
var fieldRegistry = require('web.field_registry');
|
||||
var FieldOne2Many = require('web.relational_fields').FieldOne2Many;
|
||||
var InputField = require('web.basic_fields').InputField;
|
||||
var TranslatableFieldMixin = require('web.basic_fields').TranslatableFieldMixin;
|
||||
var dom = require('web.dom');
|
||||
|
||||
var SectionAndNoteListRenderer = ListRenderer.extend({
|
||||
/**
|
||||
* We want section and note to take the whole line (except handle and trash)
|
||||
* to look better and to hide the unnecessary fields.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_renderBodyCell: function (record, node, index, options) {
|
||||
var $cell = this._super.apply(this, arguments);
|
||||
|
||||
var isSection = record.data.display_type === 'line_section';
|
||||
var isNote = record.data.display_type === 'line_note';
|
||||
|
||||
if (isSection || isNote) {
|
||||
if (node.attrs.widget === "handle") {
|
||||
return $cell;
|
||||
} else if (node.attrs.name === "name") {
|
||||
var nbrColumns = this._getNumberOfCols();
|
||||
if (this.handleField) {
|
||||
nbrColumns--;
|
||||
}
|
||||
if (this.addTrashIcon) {
|
||||
nbrColumns--;
|
||||
}
|
||||
$cell.attr('colspan', nbrColumns);
|
||||
} else {
|
||||
return $cell.addClass('o_hidden');
|
||||
}
|
||||
}
|
||||
|
||||
return $cell;
|
||||
},
|
||||
/**
|
||||
* We add the o_is_{display_type} class to allow custom behaviour both in JS and CSS.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_renderRow: function (record, index) {
|
||||
var $row = this._super.apply(this, arguments);
|
||||
|
||||
if (record.data.display_type) {
|
||||
$row.addClass('o_is_' + record.data.display_type);
|
||||
}
|
||||
|
||||
return $row;
|
||||
},
|
||||
/**
|
||||
* We want to add .o_section_and_note_list_view on the table to have stronger CSS.
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_renderView: function () {
|
||||
var def = this._super();
|
||||
this.$el.find('> table').addClass('o_section_and_note_list_view');
|
||||
return def;
|
||||
},
|
||||
});
|
||||
|
||||
// We create a custom widget because this is the cleanest way to do it:
|
||||
// to be sure this custom code will only impact selected fields having the widget
|
||||
// and not applied to any other existing ListRenderer.
|
||||
var SectionAndNoteFieldOne2Many = FieldOne2Many.extend({
|
||||
/**
|
||||
* We want to use our custom renderer for the list.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_getRenderer: function () {
|
||||
if (this.view.arch.tag === 'tree') {
|
||||
return SectionAndNoteListRenderer;
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
|
||||
// This is a merge between a FieldText and a FieldChar.
|
||||
// Indeed, we want a FieldChar for section, and a FieldText for the rest (product and note).
|
||||
var SectionAndNoteFieldText = InputField.extend(TranslatableFieldMixin, {
|
||||
/**
|
||||
* @constructor
|
||||
*/
|
||||
init: function (parent, name, record, options) {
|
||||
this.isSection = record.data.display_type === 'line_section';
|
||||
|
||||
if (!this.isSection) {
|
||||
this.className = 'o_field_text';
|
||||
this.supportedFieldTypes = ['text'];
|
||||
this.tagName = 'span';
|
||||
}
|
||||
if (this.isSection) {
|
||||
this.className = 'o_field_char';
|
||||
this.tagName = 'span';
|
||||
this.supportedFieldTypes = ['char'];
|
||||
}
|
||||
|
||||
this._super.apply(this, arguments);
|
||||
|
||||
if (!this.isSection) {
|
||||
if (this.mode === 'edit') {
|
||||
this.tagName = 'textarea';
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* As it it done in the start function, the autoresize is done only once.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
start: function () {
|
||||
if (!this.isSection) {
|
||||
if (this.mode === 'edit') {
|
||||
dom.autoresize(this.$el, {parent: this});
|
||||
|
||||
this.$el = this.$el.add(this._renderTranslateButton());
|
||||
}
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Stops the enter navigation in a text area.
|
||||
*
|
||||
* @private
|
||||
* @param {OdooEvent} ev
|
||||
*/
|
||||
_onKeydown: function (ev) {
|
||||
if (!this.isSection) {
|
||||
if (ev.which === $.ui.keyCode.ENTER) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Add translation button
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_renderEdit: function () {
|
||||
var def = this._super.apply(this, arguments);
|
||||
if (this.isSection) {
|
||||
if (this.field.size && this.field.size > 0) {
|
||||
this.$el.attr('maxlength', this.field.size);
|
||||
}
|
||||
this.$el = this.$el.add(this._renderTranslateButton());
|
||||
}
|
||||
return def;
|
||||
},
|
||||
/**
|
||||
* Trim the value input by the user.
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
* @param {any} value
|
||||
* @param {Object} [options]
|
||||
*/
|
||||
_setValue: function (value, options) {
|
||||
if (this.isSection) {
|
||||
if (this.field.trim) {
|
||||
value = value.trim();
|
||||
}
|
||||
}
|
||||
return this._super(value, options);
|
||||
},
|
||||
});
|
||||
|
||||
fieldRegistry.add('section_and_note_one2many', SectionAndNoteFieldOne2Many);
|
||||
fieldRegistry.add('section_and_note_text', SectionAndNoteFieldText);
|
||||
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
|
||||
// The goal of this file is to contain CSS hacks related to allowing
|
||||
// section and note on sale order and invoice.
|
||||
|
||||
table.o_section_and_note_list_view tr.o_data_row.o_is_line_note,
|
||||
div.oe_kanban_card.o_is_line_note {
|
||||
color: #999;
|
||||
}
|
||||
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;
|
||||
}
|
||||
table.o_section_and_note_list_view tr.o_data_row.o_is_line_section {
|
||||
border-top: 1px solid #BBB;
|
||||
border-bottom: 1px solid #BBB;
|
||||
}
|
||||
@@ -14,6 +14,8 @@
|
||||
<link rel="stylesheet" type="text/scss" href="/account/static/src/scss/account_journal_dashboard.scss"/>
|
||||
<link rel="stylesheet" type="text/scss" href="/account/static/src/scss/account_dashboard.scss"/>
|
||||
|
||||
<link rel="stylesheet" href="/account/static/src/scss/section_and_note_backend.scss"/>
|
||||
|
||||
<script type="text/javascript" src="/account/static/src/js/reconciliation/reconciliation_action.js"></script>
|
||||
<script type="text/javascript" src="/account/static/src/js/reconciliation/reconciliation_model.js"></script>
|
||||
<script type="text/javascript" src="/account/static/src/js/reconciliation/reconciliation_renderer.js"></script>
|
||||
@@ -21,6 +23,8 @@
|
||||
|
||||
<script type="text/javascript" src="/account/static/src/js/account_payment_field.js"></script>
|
||||
<script type="text/javascript" src="/account/static/src/js/account_dashboard_setup_bar.js"></script>
|
||||
|
||||
<script type="text/javascript" src="/account/static/src/js/section_and_note_fields_backend.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -47,18 +47,35 @@
|
||||
<field name="uom_id" groups="uom.group_uom"/>
|
||||
<field name="price_unit"/>
|
||||
<field name="discount" groups="base.group_no_one"/>
|
||||
<field name="price_subtotal"/>
|
||||
<field name="price_subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
|
||||
<field name="price_total" groups="account.group_show_line_subtotals_tax_included"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!--
|
||||
TODO: This view uses "parent" and will not work standalone.
|
||||
But for now we don't need it standalone.
|
||||
|
||||
The view will only work if it's called as the default view to display a one2many
|
||||
(showing invoice lines from inside the invoice form, where the invoice is the "parent").
|
||||
|
||||
If there is an action using this view directly (eg. showing all the invoice lines independently of their invoices) it will not work,
|
||||
because there is the use of parent. inside the view, which will not exist in that case.
|
||||
-->
|
||||
<record id="view_invoice_line_form" model="ir.ui.view">
|
||||
<field name="name">account.invoice.line.form</field>
|
||||
<field name="model">account.invoice.line</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Invoice Line">
|
||||
<group>
|
||||
<form>
|
||||
<field name="display_type" invisible="1"/>
|
||||
<!--
|
||||
We need the sequence field to be here for new lines to be added at the correct position.
|
||||
TODO: at some point we want to fix this in the framework so that an invisible field is not required.
|
||||
-->
|
||||
<field name="sequence" invisible="1"/>
|
||||
<group attrs="{'invisible': [('display_type', '!=', False)]}">
|
||||
<group>
|
||||
<field name="product_id"
|
||||
context="parent and {'partner_id': parent.partner_id}"/>
|
||||
@@ -70,13 +87,6 @@
|
||||
<field name="price_unit"/>
|
||||
<field name="discount" groups="base.group_no_one"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
<!--
|
||||
We need the sequence field to be here
|
||||
because we want to be able to overwrite the default sequence value in the JS
|
||||
in order for new lines to be added at the correct position.
|
||||
NOTE: at some point we want to fix this in the framework so that an invisible field is not required.
|
||||
-->
|
||||
<field name="sequence" invisible="1"/>
|
||||
</group>
|
||||
<group>
|
||||
<field domain="[('company_id', '=', parent.company_id)]" name="account_id" groups="account.group_account_user"/>
|
||||
@@ -86,7 +96,9 @@
|
||||
<field name="company_id" groups="base.group_multi_company" readonly="1"/>
|
||||
</group>
|
||||
</group>
|
||||
<label for="name"/>
|
||||
<label for="name" string="Description" attrs="{'invisible': [('display_type', '!=', False)]}"/>
|
||||
<label for="name" string="Section" attrs="{'invisible': [('display_type', '!=', 'line_section')]}"/>
|
||||
<label for="name" string="Note" attrs="{'invisible': [('display_type', '!=', 'line_note')]}"/>
|
||||
<field name="name"/>
|
||||
</form>
|
||||
</field>
|
||||
@@ -432,16 +444,33 @@
|
||||
<field name="sent" invisible="1"/>
|
||||
<notebook colspan="4">
|
||||
<page string="Invoice Lines">
|
||||
<field name="invoice_line_ids" nolabel="1" widget="one2many_list" mode="tree,kanban" context="{'type': type, 'journal_id': journal_id, 'default_invoice_id': id}">
|
||||
<field
|
||||
name="invoice_line_ids"
|
||||
nolabel="1"
|
||||
widget="section_and_note_one2many"
|
||||
mode="tree,kanban"
|
||||
context="{'type': type, 'journal_id': journal_id, 'default_invoice_id': id}"
|
||||
>
|
||||
<tree string="Invoice Lines" editable="bottom">
|
||||
<control>
|
||||
<create string="Add a line"/>
|
||||
<create string="Add a section" context="{'default_display_type': 'line_section'}"/>
|
||||
<create string="Add a note" context="{'default_display_type': 'line_note'}"/>
|
||||
</control>
|
||||
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="product_id" domain="[('sale_ok','=',True)]"/>
|
||||
<field name="origin" invisible="1"/>
|
||||
<field name="is_rounding_line" invisible="1"/>
|
||||
<field name="name"/>
|
||||
<field name="name" widget="section_and_note_text"/>
|
||||
<field name="display_type" invisible="1"/>
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="account_id" groups="account.group_account_user"
|
||||
domain="[('company_id', '=', parent.company_id), ('internal_type', '=', 'other'), ('deprecated', '=', False)]"/>
|
||||
<field
|
||||
name="account_id"
|
||||
groups="account.group_account_user"
|
||||
domain="[('company_id', '=', parent.company_id), ('internal_type', '=', 'other'), ('deprecated', '=', False)]"
|
||||
attrs="{'required': [('display_type', '=', False)]}"
|
||||
/>
|
||||
<field name="account_analytic_id" groups="analytic.group_analytic_accounting"
|
||||
domain="[('company_id', '=', parent.company_id)]"
|
||||
context="{'default_partner_id': parent.partner_id}"/>
|
||||
@@ -452,30 +481,67 @@
|
||||
<field name="discount" groups="base.group_no_one"/>
|
||||
<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"/>
|
||||
<field name="price_subtotal" string="Subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
|
||||
<field name="price_total" string="Total" groups="account.group_show_line_subtotals_tax_included"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
</tree>
|
||||
<kanban class="o_kanban_mobile">
|
||||
<field name="name"/>
|
||||
<field name="product_id"/>
|
||||
<field name="price_subtotal"/>
|
||||
<field name="price_subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
|
||||
<field name="price_total" groups="account.group_show_line_subtotals_tax_included"/>
|
||||
<field name="quantity"/>
|
||||
<field name="uom_id" groups="uom.group_uom"/>
|
||||
<field name="price_unit"/>
|
||||
<field name="display_type"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div t-attf-class="oe_kanban_card oe_kanban_global_click">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<strong><span><t t-esc="record.product_id.value or 'None'"/></span></strong>
|
||||
<div t-attf-class="oe_kanban_card oe_kanban_global_click {{ record.display_type.raw_value ? 'o_is_' + record.display_type.raw_value : '' }}">
|
||||
<t t-if="!record.display_type.raw_value">
|
||||
<div class="row">
|
||||
<div class="col-xs-8">
|
||||
<strong>
|
||||
<span>
|
||||
<t t-esc="record.product_id.value"/>
|
||||
</span>
|
||||
</strong>
|
||||
</div>
|
||||
<div class="col-xs-4">
|
||||
<strong>
|
||||
<span class="pull-right text-right">
|
||||
<t t-esc="record.price_subtotal.value" groups="account.group_show_line_subtotals_tax_excluded"/>
|
||||
<t t-esc="record.price_total.value" groups="account.group_show_line_subtotals_tax_included"/>
|
||||
</span>
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-8 text-muted">
|
||||
<span><t t-esc="record.quantity.value"/> <t t-esc="record.uom_id.value"/></span>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 text-muted">
|
||||
<span>
|
||||
Quantity:
|
||||
<t t-esc="record.quantity.value"/>
|
||||
<t t-esc="record.uom_id.value"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-4 text-muted">
|
||||
<span class="text-right"><t t-esc="record.price_subtotal.value"/></span>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 text-muted">
|
||||
<span>
|
||||
Unit Price:
|
||||
<t t-esc="record.price_unit.value"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="record.display_type.raw_value === 'line_section' || record.display_type.raw_value === 'line_note'">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<span>
|
||||
<t t-esc="record.name.value"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
@@ -60,94 +60,100 @@
|
||||
<!-- if Rounding Method is computed per line -->
|
||||
<t t-set="display_taxamount"
|
||||
t-value="o.company_id.tax_calculation_rounding_method == 'round_per_line'"/>
|
||||
<!-- HTML Preview for mobile responsive -->
|
||||
<div t-if="type_html" id="invoice_line" class="row mr0 ml0 visible-xs hidden-print">
|
||||
<table class='table table-condensed'>
|
||||
<colgroup>
|
||||
<col width='50%' />
|
||||
<col width='25%' />
|
||||
<col width='25%' />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Description</th>
|
||||
<th class="text-right">Quantity</th>
|
||||
<th class="text-right">Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr t-foreach="o.invoice_line_ids" t-as="line">
|
||||
<td>
|
||||
<span t-field="line.name"/>
|
||||
<t t-if="display_discount">
|
||||
<div>
|
||||
With a <span t-field="l.discount"/>% discount
|
||||
</div>
|
||||
</t>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-field="line.quantity"/>
|
||||
<span t-field="line.uom_id" groups="product.group_uom"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-field="line.price_subtotal"
|
||||
t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<table t-attf-class="table table-condensed #{'' if not type_html else 'hidden-xs'}" name="invoice_line_table">
|
||||
<table t-attf-class="table table-condensed " name="invoice_line_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<t t-set="colspan" t-value="6"/>
|
||||
<th>Description</th>
|
||||
<th class="hidden">Source Document</th>
|
||||
<th class="text-right">Quantity</th>
|
||||
<th class="text-right">Unit Price</th>
|
||||
<th t-if="display_discount" class="text-right">Disc.(%)</th>
|
||||
<th class="text-right">Taxes</th>
|
||||
<th t-if="display_taxamount and type_is_out" class="text-right">Tax Amount</th>
|
||||
<th class="text-right">Amount</th>
|
||||
<th class="text-right hidden-xs">Unit Price</th>
|
||||
<th t-if="display_discount" class="text-right hidden-xs">
|
||||
<span>Disc.(%)</span>
|
||||
<t t-set="colspan" t-value="colspan+1"/>
|
||||
</th>
|
||||
<th class="text-right hidden-xs">Taxes</th>
|
||||
<th t-if="display_taxamount and type_is_out" class="text-right hidden-xs">
|
||||
<span>Tax Amount</span>
|
||||
<t t-set="colspan" t-value="colspan+1"/>
|
||||
</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>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="invoice_tbody">
|
||||
<tr t-foreach="o.invoice_line_ids" t-as="l">
|
||||
<td><span t-field="l.name"/></td>
|
||||
<td class="hidden"><span t-field="l.origin"/></td>
|
||||
<td class="text-right">
|
||||
<span t-field="l.quantity"/>
|
||||
<span t-field="l.uom_id" groups="uom.group_uom"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-field="l.price_unit"/>
|
||||
</td>
|
||||
<td t-if="display_discount" class="text-right">
|
||||
<span t-field="l.discount"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="', '.join(map(lambda x: (x.description or x.name), l.invoice_line_tax_ids))"/>
|
||||
</td>
|
||||
<td t-if="display_taxamount and type_is_out" class="text-right">
|
||||
<t t-set="taxes"
|
||||
t-value="l.invoice_line_tax_ids.compute_all(l.price_unit, l.currency_id, l.quantity)"/>
|
||||
<t t-set="tax_amount"
|
||||
t-value="taxes and (taxes['total_included'] - taxes['base']) or 0.0"/>
|
||||
<span t-esc="tax_amount" t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
|
||||
</td>
|
||||
<td class="text-right" id="subtotal">
|
||||
<span t-field="l.price_subtotal"
|
||||
t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr t-foreach="range(max(5-len(o.invoice_line_ids),0))" t-as="l">
|
||||
<td t-translation="off">&nbsp;</td>
|
||||
<td class="hidden"></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td t-if="display_discount"></td>
|
||||
<td t-if="display_taxamount and type_is_out"></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tbody class="invoice_tbody">
|
||||
<t t-set="current_subtotal" t-value="0"/>
|
||||
|
||||
<t t-foreach="o.invoice_line_ids" 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"/>
|
||||
|
||||
<tr t-attf-class="{{ 'o_is_' + line.display_type if line.display_type else '' }}">
|
||||
<t t-if="not line.display_type" name="account_invoice_line_accountable">
|
||||
<td name="account_invoice_line_name"><span t-field="line.name"/></td>
|
||||
<td class="hidden"><span t-field="line.origin"/></td>
|
||||
<td class="text-right">
|
||||
<span t-field="line.quantity"/>
|
||||
<span t-field="line.uom_id" groups="uom.group_uom"/>
|
||||
</td>
|
||||
<td class="text-right hidden-xs">
|
||||
<span t-field="line.price_unit"/>
|
||||
</td>
|
||||
<td t-if="display_discount" class="text-right hidden-xs">
|
||||
<span t-field="line.discount"/>
|
||||
</td>
|
||||
<td class="text-right hidden-xs">
|
||||
<span t-esc="', '.join(map(lambda x: (x.description or x.name), line.invoice_line_tax_ids))"/>
|
||||
</td>
|
||||
<td t-if="display_taxamount and type_is_out" class="text-right hidden-xs">
|
||||
<t t-set="taxes"
|
||||
t-value="line.invoice_line_tax_ids.compute_all(line.price_unit, line.currency_id, line.quantity)"/>
|
||||
<t t-set="tax_amount"
|
||||
t-value="taxes and (taxes['total_included'] - taxes['base']) or 0.0"/>
|
||||
<span t-esc="tax_amount" t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
|
||||
</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"
|
||||
/>
|
||||
</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 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
|
||||
t-esc="current_subtotal"
|
||||
t-options='{"widget": "monetary", "display_currency": o.currency_id}'
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
</t>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<template id="l10n_in_report_invoice_document_inherit" inherit_id="account.report_invoice_document">
|
||||
|
||||
<xpath expr="//table[@name='invoice_line_table']" position="before">
|
||||
@@ -17,6 +16,7 @@
|
||||
<t t-set="tax_datas" t-value="o._invoice_line_tax_values()"/>
|
||||
</t>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//p[@t-if='o.comment']" position="before">
|
||||
<t t-if="o.company_id.country_id.code == 'IN'">
|
||||
<p id="total_in_words" class="mb16">
|
||||
@@ -25,93 +25,53 @@
|
||||
</p>
|
||||
</t>
|
||||
</xpath>
|
||||
<xpath expr="//table[@name='invoice_line_table']" position="replace">
|
||||
<table t-attf-class="table table-condensed #{'' if not type_html else 'hidden-xs'}" name="invoice_line_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Description</th>
|
||||
<th class="hidden">Source Document</th>
|
||||
<th class="text-right">Quantity</th>
|
||||
<th class="text-right">Unit Price</th>
|
||||
<th t-if="display_discount" class="text-right">Disc.(%)</th>
|
||||
<t t-if="len(o.tax_line_ids) > 0">
|
||||
<th class="text-right">Taxes</th>
|
||||
|
||||
<xpath expr="//table[@name='invoice_line_table']/thead/tr" position="inside">
|
||||
<t t-if="o.company_id.country_id.code == 'IN'">
|
||||
<th class="text-right" t-if="is_cgst">CGST<t t-set="colspan" t-value="colspan+1"/></th>
|
||||
<th class="text-right" t-if="is_cgst">SGST<t t-set="colspan" t-value="colspan+1"/></th>
|
||||
<th class="text-right" t-if="is_igst">IGST<t t-set="colspan" t-value="colspan+1"/></th>
|
||||
<th class="text-right" t-if="is_cess">CESS<t t-set="colspan" t-value="colspan+1"/></th>
|
||||
</t>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//td[@name='account_invoice_line_name']" position="after">
|
||||
<t t-if="line.product_id.l10n_in_hsn_code"><h6><strong class="ml16">HSN/SAC Code:</strong> <span t-field="line.product_id.l10n_in_hsn_code"/></h6></t>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//t[@name='account_invoice_line_accountable']" position="inside">
|
||||
<t t-if="o.company_id.country_id.code == 'IN'">
|
||||
<td class="text-right" t-if="is_cgst">
|
||||
<t t-foreach="tax_datas[line.id]" t-as="tax_data">
|
||||
<t t-if="tag_cgst_id in tax_data['tag_ids']">
|
||||
<t t-esc="tax_data['amount']" t-options="{'widget': 'monetary', 'display_currency': o.currency_id}"/>
|
||||
</t>
|
||||
<th class="text-right">Amount</th>
|
||||
<t t-if="o.company_id.country_id.code == 'IN'">
|
||||
<th class="text-right" t-if="is_cgst">CGST</th>
|
||||
<th class="text-right" t-if="is_cgst">SGST</th>
|
||||
<th class="text-right" t-if="is_igst">IGST</th>
|
||||
<th class="text-right" t-if="is_cess">CESS</th>
|
||||
</t>
|
||||
</td>
|
||||
<td class="text-right" t-if="is_cgst">
|
||||
<t t-foreach="tax_datas[line.id]" t-as="tax_data">
|
||||
<t t-if="tag_sgst_id in tax_data['tag_ids']">
|
||||
<t t-esc="tax_data['amount']" t-options="{'widget': 'monetary', 'display_currency': o.currency_id}"/>
|
||||
</t>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="invoice_tbody">
|
||||
<tr t-foreach="o.invoice_line_ids" t-as="l">
|
||||
<td><span t-field="l.name"/><t t-if="l.product_id.l10n_in_hsn_code"><h6><strong class="ml16">HSN/SAC Code:</strong> <span t-field="l.product_id.l10n_in_hsn_code"/></h6></t></td>
|
||||
<td class="hidden"><span t-field="l.origin"/></td>
|
||||
<td class="text-right">
|
||||
<span t-field="l.quantity"/>
|
||||
<span t-field="l.uom_id" groups="uom.group_uom"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-field="l.price_unit"/>
|
||||
</td>
|
||||
<td t-if="display_discount" class="text-right">
|
||||
<span t-field="l.discount"/>
|
||||
</td>
|
||||
<t t-if="len(o.tax_line_ids) > 0">
|
||||
<td class="text-right">
|
||||
<span t-esc="', '.join(map(lambda x: (x.description or x.name), l.invoice_line_tax_ids))"/>
|
||||
</td>
|
||||
</t>
|
||||
</td>
|
||||
<td class="text-right" t-if="is_igst">
|
||||
<t t-foreach="tax_datas[line.id]" t-as="tax_data">
|
||||
<t t-if="tag_igst_id in tax_data['tag_ids']">
|
||||
<t t-esc="tax_data['amount']" t-options="{'widget': 'monetary', 'display_currency': o.currency_id}"/>
|
||||
</t>
|
||||
<td class="text-right" id="subtotal">
|
||||
<span t-field="l.price_subtotal"
|
||||
t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
|
||||
</td>
|
||||
<t t-if="o.company_id.country_id.code == 'IN'">
|
||||
<td class="text-right" t-if="is_cgst">
|
||||
<t t-foreach="tax_datas[l.id]" t-as="tax_data">
|
||||
<t t-if="tag_cgst_id in tax_data['tag_ids']">
|
||||
<t t-esc="tax_data['amount']" t-options="{'widget': 'monetary', 'display_currency': o.currency_id}"/>
|
||||
</t>
|
||||
</t>
|
||||
</td>
|
||||
<td class="text-right" t-if="is_cgst">
|
||||
<t t-foreach="tax_datas[l.id]" t-as="tax_data">
|
||||
<t t-if="tag_sgst_id in tax_data['tag_ids']">
|
||||
<t t-esc="tax_data['amount']" t-options="{'widget': 'monetary', 'display_currency': o.currency_id}"/>
|
||||
</t>
|
||||
</t>
|
||||
</td>
|
||||
<td class="text-right" t-if="is_igst">
|
||||
<t t-foreach="tax_datas[l.id]" t-as="tax_data">
|
||||
<t t-if="tag_igst_id in tax_data['tag_ids']">
|
||||
<t t-esc="tax_data['amount']" t-options="{'widget': 'monetary', 'display_currency': o.currency_id}"/>
|
||||
</t>
|
||||
</t>
|
||||
</td>
|
||||
<t t-set="total_cess" t-value="0.0"/>
|
||||
<td class="text-right" t-if="is_cess">
|
||||
<t t-foreach="tax_datas[l.id]" t-as="tax_data">
|
||||
<t t-if="tag_cess_id in tax_data['tag_ids']">
|
||||
<t t-set="total_cess" t-value="total_cess + tax_data['amount']" t-options="{'widget': 'monetary', 'display_currency': o.currency_id}"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-esc="total_cess" t-options="{'widget': 'monetary', 'display_currency': o.currency_id}"/>
|
||||
</td>
|
||||
</t>
|
||||
</td>
|
||||
<td class="text-right" t-if="is_cess">
|
||||
<t t-set="total_cess" t-value="0.0"/>
|
||||
<t t-foreach="tax_datas[line.id]" t-as="tax_data">
|
||||
<t t-if="tag_cess_id in tax_data['tag_ids']">
|
||||
<t t-set="total_cess" t-value="total_cess + tax_data['amount']" t-options="{'widget': 'monetary', 'display_currency': o.currency_id}"/>
|
||||
</t>
|
||||
</tr>
|
||||
<tr t-foreach="range(max(5-len(o.invoice_line_ids),0))" t-as="l">
|
||||
<td>&nbsp;</td>
|
||||
<td class="hidden"></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td t-if="display_discount" groups="sale.group_discount_per_so_line"></td>
|
||||
<td t-att-colspan="o.company_id.country_id.code == 'IN' and 5 or 1"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</t>
|
||||
<t t-esc="total_cess" t-options="{'widget': 'monetary', 'display_currency': o.currency_id}"/>
|
||||
</td>
|
||||
</t>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//h2" position="replace">
|
||||
@@ -125,6 +85,7 @@
|
||||
<span t-field="o.number"/>
|
||||
</h2>
|
||||
</xpath>
|
||||
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<template id="gst_report_invoice_layouted_inherit" inherit_id="sale.report_invoice_layouted">
|
||||
<xpath expr="//t[@name='lines_layouted']" position="replace">
|
||||
<t groups="sale.group_sale_layout" t-foreach="o.order_lines_layouted()" t-as="page" name="lines_layouted">
|
||||
<table t-attf-class="table table-condensed #{'' if not type_html else 'hidden-xs'}">
|
||||
<thead>
|
||||
<th>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">Disc.(%)</th>
|
||||
<th class="text-right" t-if="len(o.tax_line_ids) > 0">Tax</th>
|
||||
<th class="text-right">Price</th>
|
||||
<t t-if="o.company_id.country_id.code == 'IN'">
|
||||
<th class="text-right" t-if="is_cgst">CGST</th>
|
||||
<th class="text-right" t-if="is_cgst">SGST</th>
|
||||
<th class="text-right" t-if="is_igst">IGST</th>
|
||||
<th class="text-right" t-if="is_cess">CESS</th>
|
||||
</t>
|
||||
</thead>
|
||||
<tbody class="invoice_tbody">
|
||||
<t t-foreach="page" t-as="layout_category">
|
||||
|
||||
<t t-if="layout_category_size > 1 or page_size > 1" groups="sale.group_sale_layout">
|
||||
<tr class="active">
|
||||
<td colspan="7" style="font-weight: bold; border-bottom: 1px solid black;">&bull;
|
||||
<t t-esc="layout_category['name']"/>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
|
||||
<!-- Lines associated -->
|
||||
<t t-foreach="layout_category['lines']" t-as="l">
|
||||
<tr>
|
||||
<td><span t-field="l.name"/>
|
||||
<t t-if="l.product_id.l10n_in_hsn_code"><h6><strong class="ml16">HSN/SAC Code:</strong> <span t-field="l.product_id.l10n_in_hsn_code"/></h6></t></td>
|
||||
<td class="text-right">
|
||||
<span t-field="l.quantity"/>
|
||||
<span t-field="l.uom_id" groups="uom.group_uom"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-field="l.price_unit"/>
|
||||
</td>
|
||||
<td t-if="display_discount" class="text-right" groups="sale.group_discount_per_so_line">
|
||||
<span t-field="l.discount"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="', '.join(map(lambda x: x.description or x.name, l.invoice_line_tax_ids))"/>
|
||||
</td>
|
||||
<td class="text-right" groups="account.group_show_line_subtotals_tax_excluded">
|
||||
<span t-field="l.price_subtotal"
|
||||
t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
|
||||
</td>
|
||||
<td class="text-right" groups="account.group_show_line_subtotals_tax_included">
|
||||
<span t-field="l.price_total"
|
||||
t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
|
||||
</td>
|
||||
<t t-if="o.company_id.country_id.code == 'IN'">
|
||||
<td class="text-right" t-if="is_cgst">
|
||||
<t t-foreach="tax_datas[l.id]" t-as="tax_data">
|
||||
<t t-if="tag_cgst_id in tax_data['tag_ids']">
|
||||
<t t-esc="tax_data['amount']"/>
|
||||
</t>
|
||||
</t>
|
||||
</td>
|
||||
<td class="text-right" t-if="is_cgst">
|
||||
<t t-foreach="tax_datas[l.id]" t-as="tax_data">
|
||||
<t t-if="tag_sgst_id in tax_data['tag_ids']">
|
||||
<t t-esc="tax_data['amount']"/>
|
||||
</t>
|
||||
</t>
|
||||
</td>
|
||||
<td class="text-right" t-if="is_igst">
|
||||
<t t-foreach="tax_datas[l.id]" t-as="tax_data">
|
||||
<t t-if="tag_igst_id in tax_data['tag_ids']">
|
||||
<t t-esc="tax_data['amount']"/>
|
||||
</t>
|
||||
</t>
|
||||
</td>
|
||||
<t t-set="total_cess" t-value="0.0"/>
|
||||
<td class="text-right" t-if="is_cess">
|
||||
<t t-foreach="tax_datas[l.id]" t-as="tax_data">
|
||||
<t t-if="tag_cess_id in tax_data['tag_ids']">
|
||||
<t t-set="total_cess" t-value="total_cess + tax_data['amount']"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-esc="total_cess"/>
|
||||
</td>
|
||||
</t>
|
||||
</tr>
|
||||
</t>
|
||||
|
||||
<t t-if="(layout_category_size > 1 or page_size > 1) and layout_category['subtotal']" groups="sale.group_sale_layout">
|
||||
<tr class="text-right">
|
||||
<td t-att-colspan="o.company_id.country_id.code == 'IN' and 9 or 6">
|
||||
<strong>Subtotal: </strong>
|
||||
<t t-set="subtotal" t-value="sum(line.price_subtotal for line in layout_category['lines'])"/>
|
||||
<span t-esc="subtotal" t-options="{'widget': 'monetary', 'display_currency': o.currency_id}"/>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
</t>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<t t-if="page_index < page_size - 1" groups="sale.group_sale_layout">
|
||||
<p style="page-break-before:always;"> </p>
|
||||
</t>
|
||||
</t>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
@@ -2,8 +2,8 @@
|
||||
<odoo>
|
||||
|
||||
<template id="gst_report_saleorder_document_inherit" inherit_id="sale.report_saleorder_document">
|
||||
<xpath expr="//span[@t-field='l.name']" position="after">
|
||||
<t t-if="l.product_id.l10n_in_hsn_code"><h6><strong class="ml16">HSN/SAC Code:</strong> <span t-field="l.product_id.l10n_in_hsn_code"/></h6></t>
|
||||
<xpath expr="//span[@t-field='line.name']" position="after">
|
||||
<t t-if="line.product_id.l10n_in_hsn_code"><h6><strong class="ml16">HSN/SAC Code:</strong> <span t-field="line.product_id.l10n_in_hsn_code"/></h6></t>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -48,44 +48,93 @@
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<t t-set="colspan" t-value="7"/>
|
||||
<th>Description</th>
|
||||
<th class="text-right">Taxes</th>
|
||||
<th t-if="display_taxamount and type_is_out" class="text-right">Tax Amount</th>
|
||||
<th t-if="display_taxamount and type_is_out" class="text-right">
|
||||
<span>Tax Amount</span>
|
||||
<t t-set="colspan" t-value="colspan+1"/>
|
||||
</th>
|
||||
<th class="text-right">Intrastat</th>
|
||||
<th class="text-right">Weight</th>
|
||||
<th class="text-right">Quantity</th>
|
||||
<th groups="uom.group_uom">Unit of measure</th>
|
||||
<th groups="uom.group_uom">
|
||||
<span>Unit of measure</span>
|
||||
<t t-set="colspan" t-value="colspan+1"/>
|
||||
</th>
|
||||
<th class="text-right">Unit Price</th>
|
||||
<th class="text-right" t-if="display_discount" groups="sale.group_discount_per_so_line">Discount (%)</th>
|
||||
<th class="text-right">Amount</th>
|
||||
<th class="text-right" t-if="display_discount" groups="sale.group_discount_per_so_line">
|
||||
<span>Discount (%)</span>
|
||||
<t t-set="colspan" t-value="colspan+1"/>
|
||||
</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>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr t-foreach="o.invoice_line_ids" t-as="l">
|
||||
<td><span t-field="l.name"/></td>
|
||||
<td class="text-right"><span t-esc="', '.join(map(lambda x: x.name, l.invoice_line_tax_ids))"/></td>
|
||||
<td t-if="display_taxamount and type_is_out" class="text-right">
|
||||
<t t-set="taxes"
|
||||
t-value="l.invoice_line_tax_ids.compute_all(l.price_unit, l.currency_id, l.quantity)"/>
|
||||
<t t-set="tax_amount"
|
||||
t-value="taxes and (taxes['total_included'] - taxes['base']) or 0.0"/>
|
||||
<span t-esc="tax_amount" t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
|
||||
</td>
|
||||
<td class="text-right"><span t-esc="l.product_id.intrastat_id.name"/></td>
|
||||
<td class="text-right"><span t-esc="l.product_id.weight"/></td>
|
||||
<td class="text-right"><span t-field="l.quantity"/></td>
|
||||
<td groups="uom.group_uom"><span t-field="l.uom_id"/></td>
|
||||
<td class="text-right"><span t-field="l.price_unit"/></td>
|
||||
<td t-if="display_discount" groups="sale.group_discount_per_so_line"><span t-field="l.discount"/></td>
|
||||
<td class="text-right" groups="account.group_show_line_subtotals_tax_excluded">
|
||||
<span t-field="l.price_subtotal"
|
||||
t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
|
||||
</td>
|
||||
<td class="text-right" groups="account.group_show_line_subtotals_tax_included">
|
||||
<span t-field="l.price_total"
|
||||
t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
|
||||
</td>
|
||||
</tr>
|
||||
<t t-set="current_subtotal" t-value="0"/>
|
||||
<t t-foreach="o.invoice_line_ids" 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"/>
|
||||
|
||||
<tr t-attf-class="{{ 'o_is_' + line.display_type if line.display_type else '' }}">
|
||||
<t t-if="not line.display_type">
|
||||
<td><span t-field="line.name"/></td>
|
||||
<td class="text-right"><span t-esc="', '.join(map(lambda x: x.name, line.invoice_line_tax_ids))"/></td>
|
||||
<td t-if="display_taxamount and type_is_out" class="text-right">
|
||||
<t t-set="taxes"
|
||||
t-value="line.invoice_line_tax_ids.compute_all(line.price_unit, line.currency_id, line.quantity)"/>
|
||||
<t t-set="tax_amount"
|
||||
t-value="taxes and (taxes['total_included'] - taxes['base']) or 0.0"/>
|
||||
<span t-esc="tax_amount" t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
|
||||
</td>
|
||||
<td class="text-right"><span t-esc="line.product_id.intrastat_id.name"/></td>
|
||||
<td class="text-right"><span t-esc="line.product_id.weight"/></td>
|
||||
<td class="text-right"><span t-field="line.quantity"/></td>
|
||||
<td groups="uom.group_uom"><span t-field="line.uom_id"/></td>
|
||||
<td class="text-right"><span t-field="line.price_unit"/></td>
|
||||
<td t-if="display_discount" groups="sale.group_discount_per_so_line"><span t-field="line.discount"/></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"
|
||||
/>
|
||||
</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 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
|
||||
t-esc="current_subtotal"
|
||||
t-options='{"widget": "monetary", "display_currency": o.currency_id}'
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
</t>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
@@ -23,13 +23,11 @@ This module contains all the common features of Sales Management and eCommerce.
|
||||
'data/mail_data.xml',
|
||||
'wizard/sale_make_invoice_advance_views.xml',
|
||||
'views/sale_views.xml',
|
||||
'views/account_invoice_views.xml',
|
||||
'views/sales_team_views.xml',
|
||||
'views/res_partner_views.xml',
|
||||
'views/mail_activity_views.xml',
|
||||
'views/assets.xml',
|
||||
'views/sale_portal_templates.xml',
|
||||
'views/sale_layout_category_view.xml',
|
||||
'views/res_config_settings_views.xml',
|
||||
'views/res_users_views.xml',
|
||||
],
|
||||
|
||||
@@ -6,19 +6,5 @@
|
||||
<field name="use_invoices" eval="True"/>
|
||||
<field name="dashboard_graph_model">sale.report</field>
|
||||
</record>
|
||||
|
||||
<record id="sale_layout_cat_1" model="sale.layout_category">
|
||||
<field name="name">Services</field>
|
||||
<field name="subtotal" eval="True"></field>
|
||||
<field name="pagebreak" eval="True"></field>
|
||||
<field name="sequence">1</field>
|
||||
</record>
|
||||
|
||||
<record id="sale_layout_cat_2" model="sale.layout_category">
|
||||
<field name="name">Material</field>
|
||||
<field name="subtotal" eval="True"></field>
|
||||
<field name="pagebreak" eval="False"></field>
|
||||
<field name="sequence">10</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
|
||||
@@ -10,6 +10,5 @@ from . import res_company
|
||||
from . import res_partner
|
||||
from . import sale
|
||||
from . import res_config_settings
|
||||
from . import sale_layout
|
||||
from . import sales_team
|
||||
from . import res_users
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from itertools import groupby
|
||||
from odoo import api, fields, models, _
|
||||
|
||||
|
||||
@@ -66,27 +65,6 @@ class AccountInvoice(models.Model):
|
||||
line[name] = False
|
||||
return result
|
||||
|
||||
@api.multi
|
||||
def order_lines_layouted(self):
|
||||
"""
|
||||
Returns this sales order lines ordered by sale_layout_category sequence. Used to render the report.
|
||||
"""
|
||||
self.ensure_one()
|
||||
report_pages = [[]]
|
||||
for category, lines in groupby(self.invoice_line_ids, lambda l: l.layout_category_id):
|
||||
# If last added category induced a pagebreak, this one will be on a new page
|
||||
if report_pages[-1] and report_pages[-1][-1]['pagebreak']:
|
||||
report_pages.append([])
|
||||
# Append category to current report page
|
||||
report_pages[-1].append({
|
||||
'name': category and category.name or 'Uncategorized',
|
||||
'subtotal': category and category.subtotal,
|
||||
'pagebreak': category and category.pagebreak,
|
||||
'lines': list(lines)
|
||||
})
|
||||
|
||||
return report_pages
|
||||
|
||||
@api.multi
|
||||
def get_delivery_partner_id(self):
|
||||
self.ensure_one()
|
||||
@@ -95,15 +73,13 @@ class AccountInvoice(models.Model):
|
||||
def _get_refund_common_fields(self):
|
||||
return super(AccountInvoice, self)._get_refund_common_fields() + ['team_id', 'partner_shipping_id']
|
||||
|
||||
|
||||
class AccountInvoiceLine(models.Model):
|
||||
_inherit = 'account.invoice.line'
|
||||
_order = 'invoice_id, layout_category_id, sequence, id'
|
||||
_order = 'invoice_id, sequence, id'
|
||||
|
||||
sale_line_ids = fields.Many2many(
|
||||
'sale.order.line',
|
||||
'sale_order_line_invoice_rel',
|
||||
'invoice_line_id', 'order_line_id',
|
||||
string='Sales Order Lines', readonly=True, copy=False)
|
||||
layout_category_id = fields.Many2one('sale.layout_category', string='Section')
|
||||
layout_category_sequence = fields.Integer(string='Layout Sequence')
|
||||
# TODO: remove layout_category_sequence in master or make it work properly
|
||||
|
||||
@@ -16,7 +16,6 @@ class ResConfigSettings(models.TransientModel):
|
||||
config_parameter='sale.use_sale_note')
|
||||
group_discount_per_so_line = fields.Boolean("Discounts", implied_group='sale.group_discount_per_so_line')
|
||||
module_sale_margin = fields.Boolean("Margins")
|
||||
group_sale_layout = fields.Boolean("Sections on Sales Orders", implied_group='sale.group_sale_layout')
|
||||
group_warning_sale = fields.Boolean("Sale Order Warnings", implied_group='sale.group_warning_sale')
|
||||
portal_confirmation_sign = fields.Boolean(related='company_id.portal_confirmation_sign', string='Digital Signature')
|
||||
portal_confirmation_pay = fields.Boolean(related='company_id.portal_confirmation_pay', string='Electronic Payment')
|
||||
|
||||
@@ -12,11 +12,6 @@ class ResUsers(models.Model):
|
||||
compute='_compute_groups_id', inverse='_inverse_groups_id',
|
||||
group_xml_id='sale.group_warning_sale')
|
||||
|
||||
has_group_sale_layout = fields.Boolean(
|
||||
"Personalize sales order and invoice report",
|
||||
compute='_compute_groups_id', inverse='_inverse_groups_id',
|
||||
group_xml_id='sale.group_sale_layout')
|
||||
|
||||
has_group_delivery_invoice_address = fields.Boolean(
|
||||
"Addresses in Sales Orders",
|
||||
compute='_compute_groups_id', inverse='_inverse_groups_id',
|
||||
|
||||
+43
-46
@@ -3,16 +3,13 @@
|
||||
|
||||
import uuid
|
||||
|
||||
from itertools import groupby
|
||||
from datetime import datetime, timedelta
|
||||
from werkzeug.urls import url_encode
|
||||
from datetime import datetime
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError, AccessError
|
||||
from odoo.osv import expression
|
||||
from odoo.tools import float_is_zero, float_compare, DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from odoo.tools import float_is_zero, float_compare
|
||||
|
||||
from odoo.tools.misc import formatLang
|
||||
|
||||
from odoo.addons import decimal_precision as dp
|
||||
|
||||
@@ -44,7 +41,7 @@ class SaleOrder(models.Model):
|
||||
"""
|
||||
Compute the invoice status of a SO. Possible statuses:
|
||||
- no: if the SO is not in status 'sale' or 'done', we consider that there is nothing to
|
||||
invoice. This is also hte default value if the conditions of no other status is met.
|
||||
invoice. This is also the default value if the conditions of no other status is met.
|
||||
- to invoice: if any SO line is 'to invoice', the whole SO is 'to invoice'
|
||||
- invoiced: if all SO lines are invoiced, the SO is invoiced.
|
||||
- upselling: if all SO lines are invoiced or upselling, the status is upselling.
|
||||
@@ -166,8 +163,6 @@ class SaleOrder(models.Model):
|
||||
signature = fields.Binary('Signature', help='Signature received through the portal.', copy=False, attachment=True)
|
||||
signed_by = fields.Char('Signed by', help='Name of the person that signed the SO.', copy=False)
|
||||
|
||||
product_id = fields.Many2one('product.product', related='order_line.product_id', string='Product')
|
||||
|
||||
def _compute_portal_url(self):
|
||||
super(SaleOrder, self)._compute_portal_url()
|
||||
for order in self:
|
||||
@@ -435,7 +430,14 @@ class SaleOrder(models.Model):
|
||||
|
||||
for order in self:
|
||||
group_key = order.id if grouped else (order.partner_invoice_id.id, order.currency_id.id)
|
||||
for line in order.order_line.sorted(key=lambda l: l.qty_to_invoice < 0):
|
||||
|
||||
# We only want to create sections that have at least one invoiceable line
|
||||
pending_section = None
|
||||
|
||||
for line in order.order_line:
|
||||
if line.display_type == 'line_section':
|
||||
pending_section = line
|
||||
continue
|
||||
if float_is_zero(line.qty_to_invoice, precision_digits=precision):
|
||||
continue
|
||||
if group_key not in invoices:
|
||||
@@ -451,9 +453,10 @@ class SaleOrder(models.Model):
|
||||
if order.client_order_ref and order.client_order_ref not in invoices_name[group_key]:
|
||||
invoices_name[group_key].append(order.client_order_ref)
|
||||
|
||||
if line.qty_to_invoice > 0:
|
||||
line.invoice_line_create(invoices[group_key].id, line.qty_to_invoice)
|
||||
elif line.qty_to_invoice < 0 and final:
|
||||
if line.qty_to_invoice > 0 or (line.qty_to_invoice < 0 and final):
|
||||
if pending_section:
|
||||
pending_section.invoice_line_create(invoices[group_key].id, pending_section.qty_to_invoice)
|
||||
pending_section = None
|
||||
line.invoice_line_create(invoices[group_key].id, line.qty_to_invoice)
|
||||
|
||||
if references.get(invoices.get(group_key)):
|
||||
@@ -600,28 +603,6 @@ class SaleOrder(models.Model):
|
||||
})
|
||||
order.analytic_account_id = analytic
|
||||
|
||||
@api.multi
|
||||
def order_lines_layouted(self):
|
||||
"""
|
||||
Returns this order lines classified by sale_layout_category and separated in
|
||||
pages according to the category pagebreaks. Used to render the report.
|
||||
"""
|
||||
self.ensure_one()
|
||||
report_pages = [[]]
|
||||
for category, lines in groupby(self.order_line, lambda l: l.layout_category_id):
|
||||
# If last added category induced a pagebreak, this one will be on a new page
|
||||
if report_pages[-1] and report_pages[-1][-1]['pagebreak']:
|
||||
report_pages.append([])
|
||||
# Append category to current report page
|
||||
report_pages[-1].append({
|
||||
'name': category and category.name or _('Uncategorized'),
|
||||
'subtotal': category and category.subtotal,
|
||||
'pagebreak': category and category.pagebreak,
|
||||
'lines': list(lines)
|
||||
})
|
||||
|
||||
return report_pages
|
||||
|
||||
@api.multi
|
||||
def _get_tax_amount_by_group(self):
|
||||
self.ensure_one()
|
||||
@@ -711,7 +692,7 @@ class SaleOrder(models.Model):
|
||||
class SaleOrderLine(models.Model):
|
||||
_name = 'sale.order.line'
|
||||
_description = 'Sales Order Line'
|
||||
_order = 'order_id, layout_category_id, sequence, id'
|
||||
_order = 'order_id, sequence, id'
|
||||
|
||||
@api.depends('state', 'product_uom_qty', 'qty_delivered', 'qty_to_invoice', 'qty_invoiced')
|
||||
def _compute_invoice_status(self):
|
||||
@@ -839,6 +820,9 @@ class SaleOrderLine(models.Model):
|
||||
|
||||
@api.model
|
||||
def create(self, values):
|
||||
if values.get('display_type', self.default_get(['display_type'])['display_type']):
|
||||
values.update(product_id=False, price_unit=0, product_uom_qty=0, product_uom=False, customer_lead=0)
|
||||
|
||||
values.update(self._prepare_add_missing_fields(values))
|
||||
line = super(SaleOrderLine, self).create(values)
|
||||
if line.order_id.state == 'sale':
|
||||
@@ -849,6 +833,15 @@ class SaleOrderLine(models.Model):
|
||||
self.order_id._create_analytic_account()
|
||||
return line
|
||||
|
||||
_sql_constraints = [
|
||||
('accountable_required_fields',
|
||||
"CHECK(display_type IS NOT NULL OR (product_id IS NOT NULL AND product_uom IS NOT NULL))",
|
||||
"Missing required fields on accountable sale order line."),
|
||||
('non_accountable_null_fields',
|
||||
"CHECK(display_type IS NULL OR (product_id IS NULL AND price_unit = 0 AND product_uom_qty = 0 AND product_uom IS NULL AND customer_lead = 0))",
|
||||
"Forbidden values on non-accountable sale order line"),
|
||||
]
|
||||
|
||||
def _update_line_quantity(self, values):
|
||||
orders = self.mapped('order_id')
|
||||
for order in orders:
|
||||
@@ -866,6 +859,9 @@ class SaleOrderLine(models.Model):
|
||||
|
||||
@api.multi
|
||||
def write(self, values):
|
||||
if 'display_type' in values and self.filtered(lambda line: line.display_type != values.get('display_type')):
|
||||
raise UserError("You cannot change the type of a sale order line. Instead you should delete the current line and create a new line of the proper type.")
|
||||
|
||||
if 'product_uom_qty' in values:
|
||||
precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
|
||||
self.filtered(
|
||||
@@ -910,10 +906,10 @@ class SaleOrderLine(models.Model):
|
||||
|
||||
discount = fields.Float(string='Discount (%)', digits=dp.get_precision('Discount'), default=0.0)
|
||||
|
||||
product_id = fields.Many2one('product.product', string='Product', domain=[('sale_ok', '=', True)], change_default=True, ondelete='restrict', required=True)
|
||||
product_id = fields.Many2one('product.product', string='Product', domain=[('sale_ok', '=', True)], change_default=True, ondelete='restrict')
|
||||
product_updatable = fields.Boolean(compute='_compute_product_updatable', string='Can Edit Product', readonly=True, default=True)
|
||||
product_uom_qty = fields.Float(string='Ordered Quantity', digits=dp.get_precision('Product Unit of Measure'), required=True, default=1.0)
|
||||
product_uom = fields.Many2one('uom.uom', string='Unit of Measure', required=True)
|
||||
product_uom = fields.Many2one('uom.uom', string='Unit of Measure')
|
||||
# Non-stored related field to allow portal user to see the image of the product he has ordered
|
||||
product_image = fields.Binary('Product Image', related="product_id.image", store=False)
|
||||
|
||||
@@ -957,9 +953,10 @@ class SaleOrderLine(models.Model):
|
||||
customer_lead = fields.Float(
|
||||
'Delivery Lead Time', required=True, default=0.0,
|
||||
help="Number of days between the order confirmation and the shipping of the products to the customer", oldname="delay")
|
||||
layout_category_id = fields.Many2one('sale.layout_category', string='Section')
|
||||
layout_category_sequence = fields.Integer(string='Layout Sequence')
|
||||
# TODO: remove layout_category_sequence in master or make it work properly
|
||||
|
||||
display_type = fields.Selection([
|
||||
('line_section', "Section"),
|
||||
('line_note', "Note")], default=False, help="Technical field for UX purpose.")
|
||||
|
||||
@api.multi
|
||||
@api.depends('state', 'is_expense')
|
||||
@@ -1058,12 +1055,13 @@ class SaleOrderLine(models.Model):
|
||||
self.ensure_one()
|
||||
res = {}
|
||||
account = self.product_id.property_account_income_id or self.product_id.categ_id.property_account_income_categ_id
|
||||
if not account:
|
||||
|
||||
if not account and self.product_id:
|
||||
raise UserError(_('Please define income account for this product: "%s" (id:%d) - or for its category: "%s".') %
|
||||
(self.product_id.name, self.product_id.id, self.product_id.categ_id.name))
|
||||
|
||||
fpos = self.order_id.fiscal_position_id or self.order_id.partner_id.property_account_position_id
|
||||
if fpos:
|
||||
if fpos and account:
|
||||
account = fpos.map_account(account)
|
||||
|
||||
res = {
|
||||
@@ -1076,10 +1074,10 @@ class SaleOrderLine(models.Model):
|
||||
'discount': self.discount,
|
||||
'uom_id': self.product_uom.id,
|
||||
'product_id': self.product_id.id or False,
|
||||
'layout_category_id': self.layout_category_id and self.layout_category_id.id or False,
|
||||
'invoice_line_tax_ids': [(6, 0, self.tax_id.ids)],
|
||||
'account_analytic_id': self.order_id.analytic_account_id.id,
|
||||
'analytic_tag_ids': [(6, 0, self.analytic_tag_ids.ids)],
|
||||
'display_type': self.display_type,
|
||||
}
|
||||
return res
|
||||
|
||||
@@ -1093,7 +1091,7 @@ class SaleOrderLine(models.Model):
|
||||
invoice_lines = self.env['account.invoice.line']
|
||||
precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
|
||||
for line in self:
|
||||
if not float_is_zero(qty, precision_digits=precision):
|
||||
if not float_is_zero(qty, precision_digits=precision) or not line.product_id:
|
||||
vals = line._prepare_invoice_line(qty=qty)
|
||||
vals.update({'invoice_id': invoice_id, 'sale_line_ids': [(6, 0, [line.id])]})
|
||||
invoice_lines |= self.env['account.invoice.line'].create(vals)
|
||||
@@ -1298,7 +1296,6 @@ class SaleOrderLine(models.Model):
|
||||
if discount > 0:
|
||||
self.discount = discount
|
||||
|
||||
|
||||
def _is_delivery(self):
|
||||
self.ensure_one()
|
||||
return False
|
||||
return False
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class SaleLayoutCategory(models.Model):
|
||||
_name = 'sale.layout_category'
|
||||
_order = 'sequence, id'
|
||||
|
||||
name = fields.Char('Name', required=True, translate=True)
|
||||
sequence = fields.Integer('Sequence', required=True, default=10)
|
||||
subtotal = fields.Boolean('Add subtotal', default=True)
|
||||
pagebreak = fields.Boolean('Add pagebreak')
|
||||
@@ -1,152 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<template id="report_invoice_layouted" inherit_id="account.report_invoice_document">
|
||||
<xpath expr="//table/tbody/tr/td[@id='subtotal']" position="attributes">
|
||||
<attribute name="groups">!account.group_show_line_subtotals_tax_included</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//table/tbody/tr/td[@id='subtotal']" position="after">
|
||||
<td class="text-right" groups="account.group_show_line_subtotals_tax_included">
|
||||
<span t-field="l.price_total" t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
|
||||
</td>
|
||||
</xpath>
|
||||
<xpath expr="//table[@name='invoice_line_table']" position="attributes">
|
||||
<attribute name="groups">!sale.group_sale_layout</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//div[@id='invoice_line']/table" position="attributes">
|
||||
<attribute name="groups">!sale.group_sale_layout</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//div[@id='invoice_line']/table" position="after">
|
||||
<t groups="sale.group_sale_layout" t-foreach="o.order_lines_layouted()" t-as="page" name="mobile_lines_layouted">
|
||||
<!-- HTML Preview with order line section for mobile responsive -->
|
||||
<div t-if="type_html" class="row mr0 ml0 visible-xs hidden-print">
|
||||
<table class='table table-condensed'>
|
||||
<colgroup>
|
||||
<col width='50%' />
|
||||
<col width='25%' />
|
||||
<col width='25%' />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Description</th>
|
||||
<th class="text-right">Quantity</th>
|
||||
<th class="text-right">Price</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<t t-foreach="page" t-as="layout_sections">
|
||||
<t t-if="layout_sections_size > 1 or page_size > 1" groups="sale.group_sale_layout">
|
||||
<tr class="active">
|
||||
<td colspan="7" style="font-weight: bold; border-bottom: 1px solid black;">&bull;
|
||||
<t t-esc="layout_sections['name']"/>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
<!-- Lines associated -->
|
||||
<t t-foreach="layout_sections['lines']" t-as="line">
|
||||
<tr>
|
||||
<td>
|
||||
<span t-field="line.name"/>
|
||||
<t t-if="display_discount">
|
||||
<div>
|
||||
With a <span t-field="l.discount"/>% discount
|
||||
</div>
|
||||
</t>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-field="line.quantity"/>
|
||||
<span t-field="line.uom_id" groups="product.group_uom"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-field="line.price_subtotal"
|
||||
t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
|
||||
</td>
|
||||
</tr>
|
||||
<t t-if="(layout_sections_size > 1 or page_size > 1) and layout_sections['subtotal']" groups="sale.group_sale_layout">
|
||||
<tr class="text-right">
|
||||
<td colspan="3">
|
||||
<strong>Subtotal: </strong>
|
||||
<t t-set="subtotal" t-value="sum(line.price_subtotal for line in layout_sections['lines'])"/>
|
||||
<span t-esc="subtotal" t-options="{'widget': 'monetary', 'display_currency': o.currency_id}"/>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</table>
|
||||
</div>
|
||||
</t>
|
||||
</xpath>
|
||||
<xpath expr="//table[@name='invoice_line_table']" position="after">
|
||||
<t groups="sale.group_sale_layout" t-foreach="o.order_lines_layouted()" t-as="page" name="lines_layouted">
|
||||
<table t-attf-class="table table-condensed #{'' if not type_html else 'hidden-xs'}">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>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">Disc.(%)</th>
|
||||
<th class="text-right">Taxes</th>
|
||||
<th class="text-right">Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="invoice_tbody">
|
||||
<t t-foreach="page" t-as="layout_category">
|
||||
|
||||
<t t-if="layout_category_size > 1 or page_size > 1" groups="sale.group_sale_layout">
|
||||
<tr class="active">
|
||||
<td colspan="7" style="font-weight: bold; border-bottom: 1px solid black;">&bull;
|
||||
<t t-esc="layout_category['name']"/>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
|
||||
<!-- Lines associated -->
|
||||
<t t-foreach="layout_category['lines']" t-as="l">
|
||||
<tr>
|
||||
<td><span t-field="l.name"/></td>
|
||||
<td class="text-right">
|
||||
<span t-field="l.quantity"/>
|
||||
<span t-field="l.uom_id" groups="uom.group_uom"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-field="l.price_unit"/>
|
||||
</td>
|
||||
<td t-if="display_discount" class="text-right" groups="sale.group_discount_per_so_line">
|
||||
<span t-field="l.discount"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="', '.join(map(lambda x: x.description or x.name, l.invoice_line_tax_ids))"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-field="l.price_subtotal"
|
||||
t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
|
||||
</td>
|
||||
<td class="text-right" groups="account.group_show_line_subtotals_tax_included">
|
||||
<span t-field="l.price_total"
|
||||
t-options='{"widget": "monetary", "display_currency": o.currency_id}'/>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
|
||||
<t t-if="(layout_category_size > 1 or page_size > 1) and layout_category['subtotal']" groups="sale.group_sale_layout">
|
||||
<tr class="text-right">
|
||||
<td colspan="6">
|
||||
<strong>Subtotal: </strong>
|
||||
<t t-set="subtotal" t-value="sum(line.price_subtotal for line in layout_category['lines'])"/>
|
||||
<span t-esc="subtotal" t-options="{'widget': 'monetary', 'display_currency': o.currency_id}"/>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
</t>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<t t-if="page_index < page_size - 1" groups="sale.group_sale_layout">
|
||||
<p style="page-break-before:always;"> </p>
|
||||
</t>
|
||||
</t>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="report_invoice_document_inherit_sale" inherit_id="account.report_invoice_document">
|
||||
<xpath expr="//div[@name='invoice_address']" position="attributes">
|
||||
<attribute name="groups">!sale.group_delivery_invoice_address</attribute>
|
||||
|
||||
@@ -127,6 +127,7 @@ class SaleReport(models.Model):
|
||||
self.env.cr.execute("""CREATE or REPLACE VIEW %s as (
|
||||
%s
|
||||
FROM ( %s )
|
||||
WHERE l.product_id IS NOT NULL
|
||||
%s
|
||||
)""" % (self._table, self._select(), self._from(), self._group_by()))
|
||||
|
||||
|
||||
@@ -70,83 +70,101 @@
|
||||
<t t-set="display_discount" t-value="any([l.discount for l in doc.order_line])"/>
|
||||
<!-- if Rounding Method is computed per line -->
|
||||
<t t-set="display_taxamount"
|
||||
t-value="doc.company_id.tax_calculation_rounding_method == 'round_per_line'"/>
|
||||
t-value="doc.company_id.tax_calculation_rounding_method == 'round_per_line'"
|
||||
/>
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<t t-set="colspan" t-value="5"/>
|
||||
<th>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 t-if="display_taxamount" class="text-right">
|
||||
<span>Tax Amount</span>
|
||||
<t t-set="colspan" t-value="colspan+1"/>
|
||||
</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>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="sale_tbody">
|
||||
|
||||
<t t-foreach="doc.order_lines_layouted()" t-as="page">
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>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">Disc.(%)</th>
|
||||
<th class="text-right">Taxes</th>
|
||||
<th t-if="display_taxamount" class="text-right">Tax Amount</th>
|
||||
<th class="text-right" groups="account.group_show_line_subtotals_tax_excluded">Amount</th>
|
||||
<th class="text-right price_tax_included" groups="account.group_show_line_subtotals_tax_included">Total Price</th>
|
||||
<t t-set="current_subtotal" t-value="0"/>
|
||||
|
||||
<t t-foreach="doc.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"/>
|
||||
|
||||
<tr t-attf-class="{{ 'o_is_' + line.display_type if line.display_type else '' }}">
|
||||
<t t-if="not line.display_type">
|
||||
<td><span t-field="line.name"/></td>
|
||||
<td class="text-right">
|
||||
<span t-field="line.product_uom_qty"/>
|
||||
<span t-field="line.product_uom" groups="uom.group_uom"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-field="line.price_unit"/>
|
||||
</td>
|
||||
<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">
|
||||
<span t-esc="', '.join(map(lambda x: (x.description or x.name), line.tax_id))"/>
|
||||
</td>
|
||||
<td t-if="display_taxamount" class="text-right">
|
||||
<span
|
||||
t-field="line.price_tax"
|
||||
t-options='{"widget": "monetary", "display_currency": doc.pricelist_id.currency_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"
|
||||
/>
|
||||
</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>
|
||||
</thead>
|
||||
<tbody class="sale_tbody">
|
||||
<t t-foreach="page" t-as="layout_category">
|
||||
|
||||
<t t-if="layout_category_size > 1 or page_size > 1" groups="sale.group_sale_layout">
|
||||
<tr class="active">
|
||||
<td colspan="7" style="font-weight: bold; border-bottom: 1px solid black;">&bull;
|
||||
<t t-esc="layout_category['name']"/>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
|
||||
<!-- Lines associated -->
|
||||
<t t-foreach="layout_category['lines']" t-as="l">
|
||||
<tr>
|
||||
<td><span t-field="l.name"/></td>
|
||||
<td class="text-right">
|
||||
<span t-field="l.product_uom_qty"/>
|
||||
<span t-field="l.product_uom" groups="uom.group_uom"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-field="l.price_unit"/>
|
||||
</td>
|
||||
<td t-if="display_discount" class="text-right" groups="sale.group_discount_per_so_line">
|
||||
<span t-field="l.discount"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="', '.join(map(lambda x: (x.description or x.name), l.tax_id))"/>
|
||||
</td>
|
||||
<td t-if="display_taxamount" class="text-right">
|
||||
<span t-field="l.price_tax"
|
||||
t-options='{"widget": "monetary", "display_currency": doc.pricelist_id.currency_id}'/>
|
||||
</td>
|
||||
<td class="text-right" groups="account.group_show_line_subtotals_tax_excluded">
|
||||
<span t-field="l.price_subtotal"
|
||||
t-options='{"widget": "monetary", "display_currency": doc.pricelist_id.currency_id}'/>
|
||||
</td>
|
||||
<td class="text-right" groups="account.group_show_line_subtotals_tax_included">
|
||||
<span t-field="l.price_total"
|
||||
t-options='{"widget": "monetary", "display_currency": doc.pricelist_id.currency_id}'/>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
|
||||
<t t-if="(layout_category_size > 1 or page_size > 1) and layout_category['subtotal']" groups="sale.group_sale_layout">
|
||||
<tr class="text-right">
|
||||
<td colspan="6">
|
||||
<strong>Subtotal: </strong>
|
||||
<t t-set="subtotal" t-value="sum(line.price_subtotal for line in layout_category['lines'])"/>
|
||||
<span t-esc="subtotal" t-options="{'widget': 'monetary', 'display_currency': doc.pricelist_id.currency_id}"/>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
|
||||
<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
|
||||
t-esc="current_subtotal"
|
||||
t-options='{"widget": "monetary", "display_currency": doc.pricelist_id.currency_id}'
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<t t-if="page_index < page_size - 1" groups="sale.group_sale_layout">
|
||||
<p style="page-break-before:always;"> </p>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="clearfix">
|
||||
<div class="row" name="total">
|
||||
|
||||
@@ -35,11 +35,6 @@ access_product_group_res_partner_sale_manager,res_partner group_sale_manager,bas
|
||||
access_account_move_line_salesman,account.move.line invoice,account.model_account_move_line,sales_team.group_sale_salesman,1,0,0,0
|
||||
access_sale_order_invoicing_payments,sale.order,model_sale_order,account.group_account_invoice,1,1,0,0
|
||||
access_sale_order_line_invoicing_payments,sale.order.line,model_sale_order_line,account.group_account_invoice,1,1,0,0
|
||||
report_layout_category_1,report_layout_category_1,model_sale_layout_category,sales_team.group_sale_manager,1,1,1,1
|
||||
report_layout_category_2,report_layout_category_2,model_sale_layout_category,account.group_account_manager,1,1,1,1
|
||||
report_layout_category_3,report_layout_category_3,model_sale_layout_category,sales_team.group_sale_salesman,1,1,1,0
|
||||
report_layout_category_4,report_layout_category_4,model_sale_layout_category,sales_team.group_sale_salesman_all_leads,1,1,1,0
|
||||
report_layout_category_5,report_layout_category_5,model_sale_layout_category,account.group_account_invoice,1,0,0,0
|
||||
access_product_pricelist_item_sale_manager,product.pricelist.item salemanager,product.model_product_pricelist_item,sales_team.group_sale_manager,1,1,1,1
|
||||
access_product_price_history_salemanager,prices.history sale manager,product.model_product_price_history,sales_team.group_sale_manager,1,1,1,1
|
||||
access_product_template_sale_manager,product.template salemanager,model_product_template,sales_team.group_sale_manager,1,1,1,1
|
||||
|
||||
|
@@ -1,10 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="sale.group_sale_layout" model="res.groups">
|
||||
<field name="name">Personalize sales order and invoice report</field>
|
||||
</record>
|
||||
|
||||
<record id="group_delivery_invoice_address" model="res.groups">
|
||||
<field name="name">Addresses in Sales Orders</field>
|
||||
</record>
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="invoice_form_inherit_sale" model="ir.ui.view">
|
||||
<field name="name">account.invoice.form.inherit.sale</field>
|
||||
<field name="model">account.invoice</field>
|
||||
<field name="inherit_id" ref="account.invoice_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='invoice_line_ids']/tree/field[@name='price_subtotal']" position="replace">
|
||||
<field string="Subtotal" name="price_subtotal" groups="account.group_show_line_subtotals_tax_excluded"/>
|
||||
<field string="Total" name="price_total" groups="account.group_show_line_subtotals_tax_included"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -214,22 +214,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md-6 o_setting_box">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="group_sale_layout"/>
|
||||
</div>
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="group_sale_layout"/>
|
||||
<div class="text-muted">
|
||||
Sort products in sections with subtotals and page-breaks
|
||||
</div>
|
||||
<div class="content-group" attrs="{'invisible': [('group_sale_layout','=',False)]}">
|
||||
<div class="mt16">
|
||||
<button name="%(sale.report_configuration_action)d" icon="fa-arrow-right" type="action" string="Order Line Sections" class="btn-link"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md-6 o_setting_box">
|
||||
<div class="o_setting_left_pane">
|
||||
<field name="auto_done_setting"/>
|
||||
|
||||
@@ -10,10 +10,6 @@
|
||||
<div>
|
||||
<field name='has_group_warning_sale' colspan="1" class="oe_inline"/>
|
||||
</div>
|
||||
<label for="has_group_sale_layout" colspan="3"/>
|
||||
<div>
|
||||
<field name='has_group_sale_layout' colspan="1" class="oe_inline"/>
|
||||
</div>
|
||||
<label for="has_group_delivery_invoice_address" colspan="3"/>
|
||||
<div>
|
||||
<field name='has_group_delivery_invoice_address' colspan="1" class="oe_inline"/>
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Report configuration -->
|
||||
<record id="report_configuration_form_view" model="ir.ui.view">
|
||||
<field name="name">report.configuration.form.view</field>
|
||||
<field name="model">sale.layout_category</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Report Configuration">
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="subtotal"/>
|
||||
<field name="pagebreak"/>
|
||||
<field name="sequence"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="report_configuration_tree_view" model="ir.ui.view">
|
||||
<field name="name">report.configuration.tree.view</field>
|
||||
<field name="model">sale.layout_category</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Report Configuration">
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="name"/>
|
||||
<field name="subtotal"/>
|
||||
<field name="pagebreak"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="report_configuration_search_view" model="ir.ui.view" >
|
||||
<field name="name">report.configuration.search.view</field>
|
||||
<field name="model">sale.layout_category</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Name">
|
||||
<filter string="Total" name="total" domain="[('subtotal','=','True')]"/>
|
||||
<filter string="Breaks Page" name="breaks_page" domain="[('pagebreak','=','True')]"/>
|
||||
<group string="Group By Name">
|
||||
<filter string="Name" name="group_name" context="{'group_by' : 'name'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id='report_configuration_action' model='ir.actions.act_window'>
|
||||
<field name="name">Report Configuration</field>
|
||||
<field name="res_model">sale.layout_category</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
|
||||
<menuitem
|
||||
action="report_configuration_action"
|
||||
id="Report_configuration"
|
||||
parent="menu_sales_config"
|
||||
sequence="2"
|
||||
name="Report Layout Categories"
|
||||
groups="sale.group_sale_layout"
|
||||
active="False"
|
||||
/>
|
||||
</odoo>
|
||||
@@ -217,48 +217,90 @@
|
||||
|
||||
<t t-set="display_discount" t-value="any([l.discount for l in order.order_line])"/>
|
||||
<div class="row">
|
||||
<div t-att-class="'col-md-5' if display_discount else 'col-md-6'">
|
||||
<div t-att-class="'col-lg-4 col-sm-4 col-xs-5' if display_discount else 'col-lg-5 col-sm-5 col-xs-6'">
|
||||
<strong>Product</strong>
|
||||
</div>
|
||||
<div class="hidden-sm hidden-xs col-md-2 text-right">
|
||||
<div class="col-sm-3 hidden-xs text-right">
|
||||
<strong>Unit Price</strong>
|
||||
</div>
|
||||
<div t-if="display_discount" class="hidden-sm hidden-xs col-md-1 text-right" >
|
||||
<div t-if="display_discount" class="col-xs-1 text-right" >
|
||||
<strong>Discount</strong>
|
||||
</div>
|
||||
<div class="hidden-sm hidden-xs col-md-2 text-right">
|
||||
<div class="hidden-xs col-sm-2 text-right">
|
||||
<strong>Quantity</strong>
|
||||
</div>
|
||||
<div class="hidden-sm hidden-xs col-md-2 text-right">
|
||||
<div class="col-xs-2 hidden-sm hidden-md hidden-lg text-right">
|
||||
<strong>Qty.</strong>
|
||||
</div>
|
||||
<div class="col-sm-2 col-xs-4 text-right">
|
||||
<strong>Subtotal</strong>
|
||||
</div>
|
||||
</div>
|
||||
<t t-foreach="order.order_line" t-as="ol">
|
||||
<div class="row orders_vertical_align">
|
||||
<div class="visible-lg col-lg-1 text-center">
|
||||
<span t-field="ol.product_image" t-options="{'widget': 'image', 'class': 'img-rounded', 'style':'width: 48px;height:48px'}" />
|
||||
|
||||
<t t-set="current_subtotal" t-value="0"/>
|
||||
|
||||
<t t-foreach="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"/>
|
||||
|
||||
<div t-attf-class="row orders_vertical_align {{ 'o_is_' + line.display_type if line.display_type else '' }} pt8 pb8">
|
||||
<t t-if="not line.display_type">
|
||||
<div class="visible-lg col-lg-1 text-center">
|
||||
<span t-field="line.product_image" t-options="{'widget': 'image', 'class': 'img-rounded', 'style':'width: 48px;height:48px'}" />
|
||||
</div>
|
||||
<div id='product_name' t-att-class="'col-lg-3 col-sm-4 col-xs-5' if display_discount else 'col-lg-4 col-sm-5 col-xs-6'">
|
||||
<span t-field="line.product_id.name"/>
|
||||
</div>
|
||||
<div class="col-sm-3 hidden-xs text-right">
|
||||
<span t-field="line.price_unit" t-options='{"widget": "monetary", "display_currency": order.pricelist_id.currency_id}'/>
|
||||
</div>
|
||||
<div id='product_name' t-att-class="'col-lg-3 col-sm-5 col-xs-4' if display_discount else 'col-lg-4 col-sm-6 col-xs-3'">
|
||||
<span t-esc="ol.product_id.name"/>
|
||||
<div t-if="display_discount" class="col-xs-1 text-right" style='white-space: nowrap'>
|
||||
<span t-field="line.discount"/> %
|
||||
</div>
|
||||
<div class="col-sm-3 hidden-xs text-right">
|
||||
<span t-field="ol.price_unit" t-options='{"widget": "monetary", "display_currency": order.pricelist_id.currency_id}'/>
|
||||
</div>
|
||||
<div t-if="display_discount" class="col-sm-1 hidden-xs text-right" style='white-space: nowrap'>
|
||||
<span t-field="ol.discount"/> %
|
||||
</div>
|
||||
<div class="col-xs-2 text-right">
|
||||
<t t-if="ol._name == 'sale.order.line'">
|
||||
<span t-esc="ol.product_uom_qty"/>
|
||||
</t>
|
||||
<t t-if="ol._name == 'account.invoice.line'">
|
||||
<span t-esc="ol.quantity"/>
|
||||
</t>
|
||||
</div>
|
||||
<div class="col-sm-2 col-xs-4 text-right">
|
||||
<span t-field="ol.price_subtotal" t-options='{"widget": "monetary", "display_currency": order.pricelist_id.currency_id}'/>
|
||||
</div>
|
||||
<div class="col-xs-2 text-right">
|
||||
<span t-field="line.product_uom_qty"/>
|
||||
</div>
|
||||
<div class="col-sm-2 col-xs-4 text-right">
|
||||
<span
|
||||
t-field="line.price_subtotal"
|
||||
t-options='{"widget": "monetary", "display_currency": 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": order.pricelist_id.currency_id}'
|
||||
groups="account.group_show_line_subtotals_tax_included"
|
||||
/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-elif="line.display_type == 'line_note'">
|
||||
<div class="col-xs-12">
|
||||
<span t-field="line.name"/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-elif="line.display_type == 'line_section'">
|
||||
<t t-set="current_section" t-value="line"/>
|
||||
<t t-set="current_subtotal" t-value="0"/>
|
||||
<div class="col-xs-12">
|
||||
<span t-field="line.name"/>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
<!--
|
||||
If we have a currently running subtotal, we need to display it
|
||||
when we find a new section or when we reach the end of the loop.
|
||||
-->
|
||||
<t t-if="current_section and (line_last or order.order_line[line_index+1].display_type == 'line_section')">
|
||||
<div t-attf-class="row orders_vertical_align is-subtotal pt8 pb8">
|
||||
<div class="col-xs-12 text-right">
|
||||
<strong>Subtotal:</strong> <span
|
||||
t-esc="current_subtotal"
|
||||
t-options='{"widget": "monetary", "display_currency": order.pricelist_id.currency_id}'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<hr/>
|
||||
|
||||
@@ -233,18 +233,30 @@
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Order Lines">
|
||||
<field name="order_line" mode="tree,kanban"
|
||||
attrs="{'readonly': [('state', 'in', ('done','cancel'))]}">
|
||||
<form string="Sales Order Lines">
|
||||
<field
|
||||
name="order_line"
|
||||
widget="section_and_note_one2many"
|
||||
mode="tree,kanban"
|
||||
attrs="{'readonly': [('state', 'in', ('done','cancel'))]}"
|
||||
>
|
||||
<form>
|
||||
<field name="display_type" invisible="1"/>
|
||||
<!--
|
||||
We need the sequence field to be here for new lines to be added at the correct position.
|
||||
TODO: at some point we want to fix this in the framework so that an invisible field is not required.
|
||||
-->
|
||||
<field name="sequence" invisible="1"/>
|
||||
<group>
|
||||
<group>
|
||||
<group attrs="{'invisible': [('display_type', '!=', False)]}">
|
||||
<field name="product_updatable" invisible="1"/>
|
||||
<field name="product_id"
|
||||
context="{'partner_id':parent.partner_id, 'quantity':product_uom_qty, 'pricelist':parent.pricelist_id, 'uom':product_uom, 'company_id': parent.company_id}"
|
||||
attrs="{'readonly': [('product_updatable', '=', False)]}"
|
||||
attrs="{
|
||||
'readonly': [('product_updatable', '=', False)],
|
||||
'required': [('display_type', '=', False)],
|
||||
}"
|
||||
force_save="1"
|
||||
/>
|
||||
<field name="layout_category_id" groups="sale.group_sale_layout"/>
|
||||
<field name="invoice_status" invisible="1"/>
|
||||
<field name="qty_to_invoice" invisible="1"/>
|
||||
<field name="qty_delivered_manual" invisible="1"/>
|
||||
@@ -255,8 +267,15 @@
|
||||
<field
|
||||
context="{'partner_id':parent.partner_id, 'quantity':product_uom_qty, 'pricelist':parent.pricelist_id, 'uom':product_uom, 'uom_qty_change':True, 'company_id': parent.company_id}"
|
||||
name="product_uom_qty" class="oe_inline"/>
|
||||
<field name="product_uom" groups="uom.group_uom" class="oe_inline oe_no_button"
|
||||
attrs="{'readonly': [('state', 'in', ('sale','done', 'cancel'))]}"/>
|
||||
<field
|
||||
name="product_uom"
|
||||
groups="uom.group_uom"
|
||||
class="oe_inline oe_no_button"
|
||||
attrs="{
|
||||
'readonly': [('state', 'in', ('sale', 'done', 'cancel'))],
|
||||
'required': [('display_type', '=', False)],
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
<label for="qty_delivered" string="Delivered Quantity" attrs="{'invisible': [('parent.state', 'not in', ['sale', 'done'])]}"/>
|
||||
<div attrs="{'invisible': [('parent.state', 'not in', ['sale', 'done'])]}">
|
||||
@@ -279,7 +298,7 @@
|
||||
-->
|
||||
<field name="sequence" invisible="1"/>
|
||||
</group>
|
||||
<group>
|
||||
<group attrs="{'invisible': [('display_type', '!=', False)]}">
|
||||
<field name="tax_id" widget="many2many_tags" options="{'no_create': True}" context="{'search_view_ref': 'account.account_tax_view_search'}" domain="[('type_tax_use','=','sale'),('company_id','=',parent.company_id)]"
|
||||
attrs="{'readonly': [('qty_invoiced', '>', 0)]}"/>
|
||||
<label for="customer_lead"/>
|
||||
@@ -292,45 +311,105 @@
|
||||
</div>
|
||||
</group>
|
||||
</group>
|
||||
<label for="name"/>
|
||||
<label for="name" string="Description" attrs="{'invisible': [('display_type', '!=', False)]}"/>
|
||||
<label for="name" string="Section Name (eg. Products, Services)" attrs="{'invisible': [('display_type', '!=', 'line_section')]}"/>
|
||||
<label for="name" string="Note" attrs="{'invisible': [('display_type', '!=', 'line_note')]}"/>
|
||||
<field name="name"/>
|
||||
<div groups="base.group_no_one">
|
||||
<div groups="base.group_no_one" attrs="{'invisible': [('display_type', '!=', False)]}">
|
||||
<label for="invoice_lines"/>
|
||||
<field name="invoice_lines"/>
|
||||
</div>
|
||||
<field name="state" invisible="1"/>
|
||||
</form>
|
||||
<tree string="Sales Order Lines" editable="bottom" decoration-info="invoice_status=='to invoice'">
|
||||
<field name="sequence" widget="handle"/>
|
||||
<!-- This is inherited below to make the order lines non-editable (inline)
|
||||
for the members of some usability groups (packaging, event):
|
||||
Indeed in those cases we need a dialog because there are additional fields to input.
|
||||
-->
|
||||
<tree
|
||||
string="Sales Order Lines"
|
||||
editable="bottom"
|
||||
decoration-info="(not display_type and invoice_status == 'to invoice')"
|
||||
>
|
||||
<control>
|
||||
<create string="Add a product"/>
|
||||
<create string="Add a section" context="{'default_display_type': 'line_section'}"/>
|
||||
<create string="Add a note" context="{'default_display_type': 'line_note'}"/>
|
||||
</control>
|
||||
|
||||
<field name="sequence" widget="handle" />
|
||||
<!-- We do not display the type because we don't want the user to be bothered with that information if he has no section or note. -->
|
||||
<field name="display_type" invisible="1"/>
|
||||
|
||||
<field name="product_updatable" invisible="1"/>
|
||||
<field name="product_id"
|
||||
attrs="{'readonly': [('product_updatable', '=', False)]}"
|
||||
<field
|
||||
name="product_id"
|
||||
attrs="{
|
||||
'readonly': [('product_updatable', '=', False)],
|
||||
'required': [('display_type', '=', False)],
|
||||
}"
|
||||
force_save="1"
|
||||
context="{'partner_id':parent.partner_id, 'quantity':product_uom_qty, 'pricelist':parent.pricelist_id, 'uom':product_uom, 'company_id': parent.company_id,
|
||||
'default_lst_price': price_unit, 'default_description_sale': name}"
|
||||
/>
|
||||
<field name="layout_category_id" groups="sale.group_sale_layout"/>
|
||||
<field name="name"/>
|
||||
<field name="product_uom_qty"
|
||||
string="Ordered Qty"
|
||||
context="{'partner_id':parent.partner_id, 'quantity':product_uom_qty, 'pricelist':parent.pricelist_id, 'uom':product_uom, 'company_id': parent.company_id}"
|
||||
context="{
|
||||
'partner_id': parent.partner_id,
|
||||
'quantity': product_uom_qty,
|
||||
'pricelist': parent.pricelist_id,
|
||||
'uom':product_uom,
|
||||
'company_id': parent.company_id,
|
||||
'default_lst_price': price_unit,
|
||||
'default_description_sale': name
|
||||
}"
|
||||
/>
|
||||
<field name="name" widget="section_and_note_text"/>
|
||||
<field
|
||||
name="product_uom_qty"
|
||||
string="Ordered Qty"
|
||||
context="{
|
||||
'partner_id': parent.partner_id,
|
||||
'quantity': product_uom_qty,
|
||||
'pricelist': parent.pricelist_id,
|
||||
'uom': product_uom,
|
||||
'company_id': parent.company_id
|
||||
}"
|
||||
/>
|
||||
<field
|
||||
name="qty_delivered"
|
||||
attrs="{
|
||||
'column_invisible': [('parent.state', 'not in', ['sale', 'done'])],
|
||||
'readonly': [('qty_delivered_method', '!=', 'manual')]
|
||||
}"
|
||||
/>
|
||||
<field name="qty_delivered"
|
||||
attrs="{'column_invisible': [('parent.state', 'not in', ['sale', 'done'])], 'readonly': [('qty_delivered_method', '!=', 'manual')]}"/>
|
||||
<field name="qty_delivered_manual" invisible="1"/>
|
||||
<field name="qty_delivered_method" invisible="1"/>
|
||||
<field name="qty_invoiced"
|
||||
attrs="{'column_invisible': [('parent.state', 'not in', ['sale', 'done'])]}"/>
|
||||
<field
|
||||
name="qty_invoiced"
|
||||
attrs="{'column_invisible': [('parent.state', 'not in', ['sale', 'done'])]}"
|
||||
/>
|
||||
<field name="qty_to_invoice" invisible="1"/>
|
||||
<field name="product_uom"
|
||||
attrs="{'readonly': [('state', 'in', ('sale','done', 'cancel'))]}"
|
||||
<field
|
||||
name="product_uom"
|
||||
attrs="{
|
||||
'readonly': [('state', 'in', ('sale','done', 'cancel'))],
|
||||
'required': [('display_type', '=', False)],
|
||||
}"
|
||||
context="{'company_id': parent.company_id}"
|
||||
groups="uom.group_uom" options='{"no_open": True}'/>
|
||||
<field name="analytic_tag_ids" groups="analytic.group_analytic_tags" widget="many2many_tags"/>
|
||||
<field name="price_unit"
|
||||
attrs="{'readonly': [('qty_invoiced', '>', 0)]}"/>
|
||||
<field name="tax_id" widget="many2many_tags" options="{'no_create': True}" domain="[('type_tax_use','=','sale'),('company_id','=',parent.company_id)]"
|
||||
attrs="{'readonly': [('qty_invoiced', '>', 0)]}"/>
|
||||
groups="uom.group_uom"
|
||||
options='{"no_open": True}'
|
||||
/>
|
||||
<field
|
||||
name="analytic_tag_ids"
|
||||
groups="analytic.group_analytic_tags"
|
||||
widget="many2many_tags"
|
||||
/>
|
||||
<field
|
||||
name="price_unit"
|
||||
attrs="{'readonly': [('qty_invoiced', '>', 0)]}"
|
||||
/>
|
||||
<field
|
||||
name="tax_id"
|
||||
widget="many2many_tags"
|
||||
options="{'no_create': True}"
|
||||
domain="[('type_tax_use','=','sale'),('company_id','=',parent.company_id)]"
|
||||
attrs="{'readonly': [('qty_invoiced', '>', 0)]}"
|
||||
/>
|
||||
<field name="discount" groups="sale.group_discount_per_so_line"/>
|
||||
<field name="price_subtotal" widget="monetary" groups="account.group_show_line_subtotals_tax_excluded"/>
|
||||
<field name="price_total" widget="monetary" groups="account.group_show_line_subtotals_tax_included"/>
|
||||
@@ -340,35 +419,61 @@
|
||||
<field name="currency_id" invisible="1"/>
|
||||
</tree>
|
||||
<kanban class="o_kanban_mobile">
|
||||
<field name="name"/>
|
||||
<field name="product_id"/>
|
||||
<field name="product_uom_qty"/>
|
||||
<field name="product_uom" groups="uom.group_uom"/>
|
||||
<field name="price_subtotal"/>
|
||||
<field name="price_unit"/>
|
||||
<field name="display_type"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div t-attf-class="oe_kanban_card oe_kanban_global_click">
|
||||
<div class="row">
|
||||
<div class="col-xs-8">
|
||||
<strong><span><t t-esc="record.product_id.value"/></span></strong>
|
||||
<div t-attf-class="oe_kanban_card oe_kanban_global_click {{ record.display_type.raw_value ? 'o_is_' + record.display_type.raw_value : '' }}">
|
||||
<t t-if="!record.display_type.raw_value">
|
||||
<div class="row">
|
||||
<div class="col-xs-8">
|
||||
<strong>
|
||||
<span>
|
||||
<t t-esc="record.product_id.value"/>
|
||||
</span>
|
||||
</strong>
|
||||
</div>
|
||||
<div class="col-xs-4">
|
||||
<strong>
|
||||
<span class="pull-right text-right">
|
||||
<t t-esc="record.price_subtotal.value"/>
|
||||
</span>
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-4">
|
||||
<strong><span class="pull-right text-right">
|
||||
<t t-esc="record.price_subtotal.value"/></span></strong>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 text-muted">
|
||||
<span>
|
||||
Quantity:
|
||||
<t t-esc="record.product_uom_qty.value"/>
|
||||
<t t-esc="record.product_uom.value"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 text-muted">
|
||||
<span>Quantity: <t t-esc="record.product_uom_qty.value"/> <t t-esc="record.product_uom.value"/></span>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 text-muted">
|
||||
<span>
|
||||
Unit Price:
|
||||
<t t-esc="record.price_unit.value"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 text-muted">
|
||||
<span>Unit Price: <t t-esc="record.price_unit.value"/></span>
|
||||
</t>
|
||||
<t t-if="record.display_type.raw_value === 'line_section' || record.display_type.raw_value === 'line_note'">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<span>
|
||||
<t t-esc="record.name.value"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
<hr/>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
@@ -439,7 +544,11 @@
|
||||
<field name="user_id"/>
|
||||
<field name="team_id" string="Sales Channel"/>
|
||||
<field name="analytic_account_id"/>
|
||||
<field name="product_id"/>
|
||||
<!-- We only allow to search on the following sale order line fields (product, name) because the other fields, such as price, quantity, ...
|
||||
will not be searched as often, and if they need to be searched it's usually in the context of products
|
||||
and then they can be searched from the page listing the sale order lines related to a product (from the product itself).
|
||||
-->
|
||||
<field name="order_line" string="Product" filter_domain="[('order_line.product_id.name', 'ilike', self)]"/>
|
||||
<filter string="My Orders" domain="[('user_id','=',uid)]" name="my_sale_orders_filter"/>
|
||||
<separator/>
|
||||
<filter string="Unread Messages" name="message_needaction" domain="[('message_needaction','=',True)]"/>
|
||||
@@ -606,7 +715,6 @@
|
||||
<field name="model">sale.order.line</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Sales Order Lines" create="false">
|
||||
<field name="product_id" invisible="1"/>
|
||||
<field name="order_id"/>
|
||||
<field name="order_partner_id"/>
|
||||
<field name="name"/>
|
||||
@@ -742,9 +850,6 @@
|
||||
<xpath expr="//field[@name='partner_id']" position="after">
|
||||
<field name="partner_shipping_id" groups="sale.group_delivery_invoice_address"/>
|
||||
</xpath>
|
||||
<xpath expr="//page/field[@name='invoice_line_ids']/tree/field[@name='product_id']" position="after">
|
||||
<field name="layout_category_id" groups="sale.group_sale_layout"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='discount']" position="attributes">
|
||||
<attribute name="groups">sale.group_discount_per_so_line</attribute>
|
||||
</xpath>
|
||||
@@ -771,9 +876,6 @@
|
||||
<field name="inherit_id" ref="account.view_invoice_line_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<data>
|
||||
<xpath expr="//group/group/field[@name='product_id']" position="after">
|
||||
<field name="layout_category_id" groups="sale.group_sale_layout"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='discount']" position="attributes">
|
||||
<attribute name="groups">sale.group_discount_per_so_line</attribute>
|
||||
</xpath>
|
||||
|
||||
@@ -6,9 +6,6 @@
|
||||
<record id="sale.menu_report_product_all" model="ir.ui.menu">
|
||||
<field name="active" eval="True"/>
|
||||
</record>
|
||||
<record id="sale.Report_configuration" model="ir.ui.menu">
|
||||
<field name="active" eval="True"/>
|
||||
</record>
|
||||
<record id="sale.menu_sales_config" model="ir.ui.menu">
|
||||
<field name="active" eval="True"/>
|
||||
</record>
|
||||
|
||||
@@ -71,4 +71,8 @@
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.table > tbody > tr.o_is_line_section td {
|
||||
border-color: black;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,12 +39,10 @@
|
||||
thead {
|
||||
color: $o-brand-primary;
|
||||
tr th {
|
||||
text-align: left !important;
|
||||
border-top: 3px solid $o-brand-primary !important;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
tr th:last-child {
|
||||
text-align: right !important;
|
||||
}
|
||||
tr th:first-child {
|
||||
width: 48%;
|
||||
@@ -61,10 +59,8 @@
|
||||
tr {
|
||||
td {
|
||||
padding: 15px 5px;
|
||||
text-align: left !important;
|
||||
}
|
||||
td:last-child {
|
||||
text-align: right !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,3 +33,21 @@ body {
|
||||
.hidden-print{
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
The goal of the following lines is to contain CSS hacks related to allowing
|
||||
section and note on sale order and invoice.
|
||||
*/
|
||||
.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;
|
||||
}
|
||||
|
||||
.row.o_is_line_note,
|
||||
tr.o_is_line_note {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
@@ -149,7 +149,6 @@ class sale_quote(http.Controller):
|
||||
'name': Option.name,
|
||||
'order_id': Order.id,
|
||||
'product_id': Option.product_id.id,
|
||||
'layout_category_id': Option.layout_category_id.id,
|
||||
'product_uom_qty': Option.quantity,
|
||||
'product_uom': Option.uom_id.id,
|
||||
'discount': Option.discount,
|
||||
|
||||
@@ -22,9 +22,11 @@ class SaleOrderLine(models.Model):
|
||||
@api.onchange('product_id')
|
||||
def product_id_change(self):
|
||||
domain = super(SaleOrderLine, self).product_id_change()
|
||||
if self.order_id.template_id:
|
||||
self.name = next((quote_line.name for quote_line in self.order_id.template_id.quote_line if
|
||||
quote_line.product_id.id == self.product_id.id), self.name)
|
||||
if self.product_id and self.order_id.template_id:
|
||||
for quote_line in self.order_id.template_id.quote_line:
|
||||
if quote_line.product_id == self.product_id:
|
||||
self.name = quote_line.name
|
||||
break
|
||||
return domain
|
||||
|
||||
@api.model
|
||||
@@ -124,30 +126,33 @@ class SaleOrder(models.Model):
|
||||
|
||||
order_lines = [(5, 0, 0)]
|
||||
for line in template.quote_line:
|
||||
discount = 0
|
||||
if self.pricelist_id:
|
||||
price = self.pricelist_id.with_context(uom=line.product_uom_id.id).get_product_price(line.product_id, 1, False)
|
||||
if self.pricelist_id.discount_policy == 'without_discount' and line.price_unit:
|
||||
discount = (line.price_unit - price) / line.price_unit * 100
|
||||
price = line.price_unit
|
||||
|
||||
else:
|
||||
price = line.price_unit
|
||||
|
||||
data = {
|
||||
'display_type': line.display_type,
|
||||
'name': line.name,
|
||||
'price_unit': price,
|
||||
'discount': 100 - ((100 - discount) * (100 - line.discount)/100),
|
||||
'product_uom_qty': line.product_uom_qty,
|
||||
'product_id': line.product_id.id,
|
||||
'layout_category_id': line.layout_category_id,
|
||||
'product_uom': line.product_uom_id.id,
|
||||
'website_description': line.website_description,
|
||||
'state': 'draft',
|
||||
'customer_lead': self._get_customer_lead(line.product_id.product_tmpl_id),
|
||||
}
|
||||
if self.pricelist_id:
|
||||
data.update(self.env['sale.order.line']._get_purchase_price(self.pricelist_id, line.product_id, line.product_uom_id, fields.Date.context_today(self)))
|
||||
if line.product_id:
|
||||
discount = 0
|
||||
if self.pricelist_id:
|
||||
price = self.pricelist_id.with_context(uom=line.product_uom_id.id).get_product_price(line.product_id, 1, False)
|
||||
if self.pricelist_id.discount_policy == 'without_discount' and line.price_unit:
|
||||
discount = (line.price_unit - price) / line.price_unit * 100
|
||||
price = line.price_unit
|
||||
|
||||
else:
|
||||
price = line.price_unit
|
||||
|
||||
data.update({
|
||||
'price_unit': price,
|
||||
'discount': 100 - ((100 - discount) * (100 - line.discount)/100),
|
||||
'product_uom_qty': line.product_uom_qty,
|
||||
'product_id': line.product_id.id,
|
||||
'product_uom': line.product_uom_id.id,
|
||||
'customer_lead': self._get_customer_lead(line.product_id.product_tmpl_id),
|
||||
})
|
||||
if self.pricelist_id:
|
||||
data.update(self.env['sale.order.line']._get_purchase_price(self.pricelist_id, line.product_id, line.product_uom_id, fields.Date.context_today(self)))
|
||||
order_lines.append((0, 0, data))
|
||||
|
||||
self.order_line = order_lines
|
||||
@@ -161,7 +166,6 @@ class SaleOrder(models.Model):
|
||||
price = option.price_unit
|
||||
data = {
|
||||
'product_id': option.product_id.id,
|
||||
'layout_category_id': option.layout_category_id,
|
||||
'name': option.name,
|
||||
'quantity': option.quantity,
|
||||
'uom_id': option.uom_id.id,
|
||||
@@ -262,7 +266,6 @@ class SaleOrderOption(models.Model):
|
||||
line_id = fields.Many2one('sale.order.line', on_delete="set null")
|
||||
name = fields.Text('Description', required=True)
|
||||
product_id = fields.Many2one('product.product', 'Product', domain=[('sale_ok', '=', True)])
|
||||
layout_category_id = fields.Many2one('sale.layout_category', string='Section')
|
||||
website_description = fields.Html('Line Description', sanitize_attributes=False, translate=html_translate)
|
||||
price_unit = fields.Float('Unit Price', required=True, digits=dp.get_precision('Product Price'))
|
||||
discount = fields.Float('Discount (%)', digits=dp.get_precision('Discount'))
|
||||
@@ -306,7 +309,6 @@ class SaleOrderOption(models.Model):
|
||||
'name': self.name,
|
||||
'order_id': order.id,
|
||||
'product_id': self.product_id.id,
|
||||
'layout_category_id': self.layout_category_id.id,
|
||||
'product_uom_qty': self.quantity,
|
||||
'product_uom': self.uom_id.id,
|
||||
'discount': self.discount,
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.tools.translate import html_translate
|
||||
from odoo.addons import decimal_precision as dp
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
from odoo.exceptions import ValidationError, UserError
|
||||
|
||||
class SaleQuoteTemplate(models.Model):
|
||||
_name = "sale.quote.template"
|
||||
@@ -55,21 +54,24 @@ class SaleQuoteTemplate(models.Model):
|
||||
class SaleQuoteLine(models.Model):
|
||||
_name = "sale.quote.line"
|
||||
_description = "Quotation Template Lines"
|
||||
_order = 'sequence, id'
|
||||
_order = 'quote_id, sequence, id'
|
||||
|
||||
sequence = fields.Integer('Sequence', help="Gives the sequence order when displaying a list of sale quote lines.",
|
||||
default=10)
|
||||
quote_id = fields.Many2one('sale.quote.template', 'Quotation Template Reference', required=True,
|
||||
ondelete='cascade', index=True)
|
||||
name = fields.Text('Description', required=True, translate=True)
|
||||
product_id = fields.Many2one('product.product', 'Product', domain=[('sale_ok', '=', True)], required=True)
|
||||
layout_category_id = fields.Many2one('sale.layout_category', string='Section')
|
||||
product_id = fields.Many2one('product.product', 'Product', domain=[('sale_ok', '=', True)])
|
||||
website_description = fields.Html('Line Description', related='product_id.product_tmpl_id.quote_description',
|
||||
translate=html_translate)
|
||||
price_unit = fields.Float('Unit Price', required=True, digits=dp.get_precision('Product Price'))
|
||||
discount = fields.Float('Discount (%)', digits=dp.get_precision('Discount'), default=0.0)
|
||||
product_uom_qty = fields.Float('Quantity', required=True, digits=dp.get_precision('Product UoS'), default=1)
|
||||
product_uom_id = fields.Many2one('uom.uom', 'Unit of Measure ', required=True)
|
||||
product_uom_id = fields.Many2one('uom.uom', 'Unit of Measure')
|
||||
|
||||
display_type = fields.Selection([
|
||||
('line_section', "Section"),
|
||||
('line_note', "Note")], default=False, help="Technical field for UX purpose.")
|
||||
|
||||
@api.onchange('product_id')
|
||||
def _onchange_product_id(self):
|
||||
@@ -92,11 +94,15 @@ class SaleQuoteLine(models.Model):
|
||||
|
||||
@api.model
|
||||
def create(self, values):
|
||||
if values.get('display_type', self.default_get(['display_type'])['display_type']):
|
||||
values.update(product_id=False, price_unit=0, product_uom_qty=0, product_uom_id=False)
|
||||
values = self._inject_quote_description(values)
|
||||
return super(SaleQuoteLine, self).create(values)
|
||||
|
||||
@api.multi
|
||||
def write(self, values):
|
||||
if 'display_type' in values and self.filtered(lambda line: line.display_type != values.get('display_type')):
|
||||
raise UserError("You cannot change the type of a sale quote line. Instead you should delete the current line and create a new line of the proper type.")
|
||||
values = self._inject_quote_description(values)
|
||||
return super(SaleQuoteLine, self).write(values)
|
||||
|
||||
@@ -107,6 +113,16 @@ class SaleQuoteLine(models.Model):
|
||||
values['website_description'] = product.quote_description or product.website_description or ''
|
||||
return values
|
||||
|
||||
_sql_constraints = [
|
||||
('accountable_product_id_required',
|
||||
"CHECK(display_type IS NOT NULL OR (product_id IS NOT NULL AND product_uom_id IS NOT NULL))",
|
||||
"Missing required product and UoM on accountable sale quote line."),
|
||||
|
||||
('non_accountable_fields_null',
|
||||
"CHECK(display_type IS NULL OR (product_id IS NULL AND price_unit = 0 AND product_uom_qty = 0 AND product_uom_id IS NULL))",
|
||||
"Forbidden product, unit price, quantity, and UoM on non-accountable sale quote line"),
|
||||
]
|
||||
|
||||
|
||||
class SaleQuoteOption(models.Model):
|
||||
_name = "sale.quote.option"
|
||||
@@ -116,7 +132,6 @@ class SaleQuoteOption(models.Model):
|
||||
index=True, required=True)
|
||||
name = fields.Text('Description', required=True, translate=True)
|
||||
product_id = fields.Many2one('product.product', 'Product', domain=[('sale_ok', '=', True)], required=True)
|
||||
layout_category_id = fields.Many2one('sale.layout_category', string='Section')
|
||||
website_description = fields.Html('Option Description', translate=html_translate, sanitize_attributes=False)
|
||||
price_unit = fields.Float('Unit Price', required=True, digits=dp.get_precision('Product Price'))
|
||||
discount = fields.Float('Discount (%)', digits=dp.get_precision('Discount'))
|
||||
|
||||
@@ -42,13 +42,6 @@
|
||||
table-layout: auto !important;
|
||||
}
|
||||
|
||||
.wq-table th, .wq-table td {
|
||||
border-top: none !important;
|
||||
}
|
||||
|
||||
.wq-table:not(:first-of-type) thead {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.o_website_quote .bs-sidebar .nav > .active > ul {
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
<tree string="Sales Quotation Template Lines" editable="bottom">
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="product_id"/>
|
||||
<field name="layout_category_id" groups="sale.group_sale_layout"/>
|
||||
<field name="name"/>
|
||||
<field name="quantity"/>
|
||||
<field name="uom_id" groups="uom.group_uom"/>
|
||||
|
||||
@@ -38,12 +38,22 @@
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Lines">
|
||||
<field name="quote_line">
|
||||
<field name="quote_line" widget="section_and_note_one2many">
|
||||
<form string="Sales Quotation Template Lines">
|
||||
<group>
|
||||
<!--
|
||||
We need the sequence field to be here for new lines to be added at the correct position.
|
||||
TODO: at some point we want to fix this in the framework so that an invisible field is not required.
|
||||
-->
|
||||
<field name="sequence" invisible="1"/>
|
||||
<field name="display_type" invisible="1"/>
|
||||
<group attrs="{'invisible': [('display_type', '!=', False)]}">
|
||||
<group>
|
||||
<field name="product_id"/>
|
||||
<field name="layout_category_id" groups="sale.group_sale_layout"/>
|
||||
<field
|
||||
name="product_id"
|
||||
attrs="{
|
||||
'required': [('display_type', '=', False)]
|
||||
}"
|
||||
/>
|
||||
<label for="product_uom_qty"/>
|
||||
<div>
|
||||
<field
|
||||
@@ -57,7 +67,13 @@
|
||||
</group>
|
||||
</group>
|
||||
<notebook colspan="4">
|
||||
<page string="Description">
|
||||
<page string="Description" attrs="{'invisible': [('display_type', '!=', False)]}">
|
||||
<field name="name" />
|
||||
</page>
|
||||
<page string="Section" attrs="{'invisible': [('display_type', '!=', 'line_section')]}">
|
||||
<field name="name" />
|
||||
</page>
|
||||
<page string="Note" attrs="{'invisible': [('display_type', '!=', 'line_note')]}">
|
||||
<field name="name" />
|
||||
</page>
|
||||
<page string="Website Description">
|
||||
@@ -66,12 +82,24 @@
|
||||
</notebook>
|
||||
</form>
|
||||
<tree string="Sales Quotation Template Lines" editable="bottom">
|
||||
<control>
|
||||
<create string="Add a product"/>
|
||||
<create string="Add a section" context="{'default_display_type': 'line_section'}"/>
|
||||
<create string="Add a note" context="{'default_display_type': 'line_note'}"/>
|
||||
</control>
|
||||
|
||||
<field name="display_type" invisible="1"/>
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="product_id"/>
|
||||
<field name="layout_category_id" groups="sale.group_sale_layout"/>
|
||||
<field name="name"/>
|
||||
<field name="name" widget="section_and_note_text"/>
|
||||
<field name="product_uom_qty"/>
|
||||
<field name="product_uom_id" groups="uom.group_uom"/>
|
||||
<field
|
||||
name="product_uom_id"
|
||||
groups="uom.group_uom"
|
||||
attrs="{
|
||||
'required': [('display_type', '=', False)]
|
||||
}"
|
||||
/>
|
||||
<field name="discount" groups="sale.group_discount_per_so_line"/>
|
||||
<field name="price_unit" invisible="1"/>
|
||||
<field name="website_description" invisible="1"/>
|
||||
@@ -82,7 +110,6 @@
|
||||
<field name="options">
|
||||
<tree string="Sales Quotation Template Lines" editable="bottom">
|
||||
<field name="product_id"/>
|
||||
<field name="layout_category_id" groups="sale.group_sale_layout"/>
|
||||
<field name="name"/>
|
||||
<field name="quantity"/>
|
||||
<field name="uom_id" groups="uom.group_uom"/>
|
||||
|
||||
@@ -17,127 +17,162 @@
|
||||
<template id="pricing" name="Price">
|
||||
<section id="quote">
|
||||
<h1 class="page-header">Pricing</h1>
|
||||
<t t-foreach="quotation.order_lines_layouted()" t-as="page">
|
||||
<table class="table wq-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Products</th>
|
||||
<th>Quantity</th>
|
||||
<th><span t-if="any([line.tax_id for line in quotation.order_line])">Taxes</span></th>
|
||||
<th><span t-if="True in [line.discount > 0 for line in quotation.order_line]">Discount</span></th>
|
||||
<th class="text-right">Unit Price</th>
|
||||
<th class="text-right" groups="account.group_show_line_subtotals_tax_excluded">Amount</th>
|
||||
<th class="text-right price_tax_included" groups="account.group_show_line_subtotals_tax_included">Total Price</th>
|
||||
<th></th>
|
||||
|
||||
<t t-set="display_taxamount" t-value="any([line.tax_id for line in quotation.order_line])"/>
|
||||
<t t-set="display_discount" t-value="True in [line.discount > 0 for line in quotation.order_line]"/>
|
||||
|
||||
<table class="table wq-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<t t-set="colspan" t-value="5"/>
|
||||
<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>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="sale_tbody">
|
||||
|
||||
<t t-set="current_subtotal" t-value="0"/>
|
||||
|
||||
<t t-foreach="quotation.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"/>
|
||||
|
||||
<tr t-attf-class="{{ 'o_is_' + line.display_type if line.display_type else '' }}">
|
||||
<t t-if="not line.display_type">
|
||||
<td><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": quotation.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": quotation.pricelist_id.currency_id}'/>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span
|
||||
t-field="line.price_subtotal"
|
||||
t-options='{"widget": "monetary", "display_currency": quotation.pricelist_id.currency_id}'
|
||||
groups="account.group_show_line_subtotals_tax_excluded"
|
||||
/>
|
||||
<span
|
||||
t-field="line.price_total"
|
||||
t-options='{"widget": "monetary", "display_currency": quotation.pricelist_id.currency_id}'
|
||||
groups="account.group_show_line_subtotals_tax_included"
|
||||
/>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a t-attf-href="./update_line/#{ line.id }/?order_id=#{ quotation.id }&unlink=True&token=#{ quotation.access_token }" class="mb8 js_update_line_json hidden-print" t-if="line.option_line_id" aria-label="Remove" title="Remove">
|
||||
<span class="fa fa-trash-o"></span>
|
||||
</a>
|
||||
</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 quotation.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": quotation.pricelist_id.currency_id}'
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
</t>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="clearfix">
|
||||
<div id="total" class="row" name="total">
|
||||
<div t-attf-class="col-sm-4 pull-right #{'col-xs-4' if not type_html else ''}">
|
||||
<table class="table table-condensed" style="min-width: 250px;max-width: 400px;">
|
||||
<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="quotation.amount_untaxed"
|
||||
t-options='{"widget": "monetary","display_currency": quotation.pricelist_id.currency_id}'
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<t t-foreach="quotation._get_tax_amount_by_group()" t-as="amount_by_group">
|
||||
<tr style="border-bottom:1px solid #dddddd;">
|
||||
<t t-if="amount_by_group[3] == 1 and quotation.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": quotation.pricelist_id.currency_id}'/></span>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="amount_by_group[1]"
|
||||
t-options='{"widget": "monetary", "display_currency": quotation.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": quotation.pricelist_id.currency_id}'/>
|
||||
</td>
|
||||
</t>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<t t-foreach="page" t-as="layout_category">
|
||||
|
||||
<t t-if="layout_category_size > 1 or page_size > 1" groups="sale.group_sale_layout">
|
||||
<tr class="active">
|
||||
<td colspan="7" style="font-weight: bold; border-bottom: 1px solid black;">&bull;
|
||||
<t t-esc="layout_category['name']"/>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
|
||||
<!-- Lines associated -->
|
||||
<t t-foreach="layout_category['lines']" t-as="line">
|
||||
<tr>
|
||||
<td>
|
||||
<span t-field="line.name"/>
|
||||
</td>
|
||||
<td>
|
||||
<div id="quote_qty">
|
||||
<span t-field="line.product_uom_qty"/>
|
||||
<span t-field="line.product_uom"/>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div t-foreach="line.tax_id" t-as="tax">
|
||||
<t t-esc="tax.name"/>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<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": quotation.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": quotation.pricelist_id.currency_id}'/>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-right" groups="account.group_show_line_subtotals_tax_excluded">
|
||||
<span t-field="line.price_subtotal"
|
||||
t-options='{"widget": "monetary", "display_currency": quotation.pricelist_id.currency_id}'/>
|
||||
</td>
|
||||
<td class="text-right" groups="account.group_show_line_subtotals_tax_included">
|
||||
<span t-field="line.price_total"
|
||||
t-options='{"widget": "monetary", "display_currency": quotation.pricelist_id.currency_id}'/>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a t-attf-href="./update_line/#{ line.id }/?order_id=#{ quotation.id }&unlink=True&token=#{ quotation.access_token }" class="mb8 js_update_line_json hidden-print" t-if="line.option_line_id" aria-label="Remove" title="Remove">
|
||||
<span class="fa fa-trash-o"></span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
|
||||
<t t-if="(layout_category_size > 1 or page_size > 1) and layout_category['subtotal']" groups="sale.group_sale_layout">
|
||||
<tr>
|
||||
<td></td><td></td><td></td><td></td>
|
||||
<td class="text-right" style="padding-bottom: 32px"><strong>Subtotal:</strong></td>
|
||||
<td class="text-right" style="padding-bottom: 32px">
|
||||
<t t-set="subtotal" t-value="sum(line.price_subtotal for line in layout_category['lines'])"/>
|
||||
<strong data-id="total_amount" t-esc="subtotal" t-options="{'widget': 'monetary', 'display_currency': quotation.pricelist_id.currency_id}"/>
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</t>
|
||||
</t>
|
||||
<t t-if="page_index == page_size - 1">
|
||||
<t t-if="any([line.tax_id for line in quotation.order_line])">
|
||||
<tr>
|
||||
<td></td><td></td><td></td><td></td>
|
||||
<td class="text-right"><strong>Subtotal:</strong></td>
|
||||
<td class="text-right">
|
||||
<strong data-id="total_amount" t-field="quotation.amount_untaxed" t-options='{"widget": "monetary","display_currency": quotation.pricelist_id.currency_id}'/>
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td></td><td></td><td></td><td></td>
|
||||
<td class="text-right">Taxes:</td>
|
||||
<td class="text-right">
|
||||
<span data-id="total_amount" t-field="quotation.amount_tax" t-options='{"widget": "monetary","display_currency": quotation.pricelist_id.currency_id}'/>
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</t>
|
||||
<tr>
|
||||
<td></td><td></td><td></td><td></td>
|
||||
<td class="text-right"><strong>Total:</strong></td>
|
||||
<td class="text-right">
|
||||
<strong data-id="total_amount" t-field="quotation.amount_total" t-options='{"widget": "monetary","display_currency": quotation.pricelist_id.currency_id}'/>
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</t>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<t t-if="page_index < page_size - 1" groups="sale.group_sale_layout">
|
||||
<p style="page-break-before:always;"> </p>
|
||||
</t>
|
||||
|
||||
</t>
|
||||
</t>
|
||||
<tr class="border-black">
|
||||
<td><strong>Total</strong></td>
|
||||
<td class="text-right">
|
||||
<span data-id="total_amount" t-field="quotation.amount_total" t-options='{"widget": "monetary", "display_currency": quotation.pricelist_id.currency_id}'/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="terms" class="container" t-if="quotation.note">
|
||||
<h2 class="page-header">Terms & Conditions</h2>
|
||||
|
||||
@@ -1614,11 +1614,11 @@
|
||||
|
||||
<template id="portal_order_page_products_links" name="Orders Followup Products Links" inherit_id="sale.portal_order_page">
|
||||
<xpath expr="//div[@id='product_name']/*" position="replace">
|
||||
<a t-if="ol.product_id.website_published" t-att-href="ol.product_id.website_url">
|
||||
<span t-esc="ol.name" />
|
||||
<a t-if="line.product_id.website_published" t-att-href="line.product_id.website_url">
|
||||
<span t-field="line.name" />
|
||||
</a>
|
||||
<t t-if="not ol.product_id.website_published">
|
||||
<span t-esc="ol.name" />
|
||||
<t t-if="not line.product_id.website_published">
|
||||
<span t-field="line.name" />
|
||||
</t>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<odoo>
|
||||
<template id="portal_order_page_downloads" name="Orders Downloads Followup" inherit_id="sale.portal_order_page">
|
||||
<xpath expr="//div[@id='product_name']" position="inside">
|
||||
<t t-set="attachments" t-value="digital_attachments.get(ol.product_id.id)"/>
|
||||
<t t-set="attachments" t-value="digital_attachments.get(line.product_id.id)"/>
|
||||
<t t-if="attachments">
|
||||
<span class="dropdown">
|
||||
<button class="btn btn-xs btn-default dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown">
|
||||
|
||||
Reference in New Issue
Block a user