[MERGE] sale_timesheet: Improve project overview
1) Add the following stat buttons on the right of the Tasks one: - (fa-tasks) Late Tasks: tasks whose deadline is exceeded (do not include tasks that don't have a deadline set) - (fa-tasks) Tasks in Overtime: tasks whose Effective hours are strictly greater than their Planned hours (if the latter is stricly greater than 0) 2) Remove the sheet view (should be like financial reports) 3) Add an 'Other Revenues' metric under the Profitability section below 'To Invoice' - It should represent all the revenues other than from timesheets linked to the analytic account of the project. - Only visible if greater than 0 4) Allow to fold/unfold the details by SO. It should be folded by default. Note: Although qweb views have a generic mechanism to manage fold and unfold, this standard behavior is not used here. It would imply refactoring the entire code preparing the template data (700+ lines of code which are hard to read). The quick and dirty way is used for now. (I wish good luck for the dev if it's one day necessary.) Task 2119567 -- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr closes odoo/odoo#40852 Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com>
This commit is contained in:
@@ -88,6 +88,7 @@ class Task(models.Model):
|
||||
effective_hours = fields.Float("Hours Spent", compute='_compute_effective_hours', compute_sudo=True, store=True, help="Computed using the sum of the task work done.")
|
||||
total_hours_spent = fields.Float("Total Hours", compute='_compute_total_hours_spent', store=True, help="Computed as: Time Spent + Sub-tasks Hours.")
|
||||
progress = fields.Float("Progress", compute='_compute_progress_hours', store=True, group_operator="avg", help="Display progress of current task.")
|
||||
overtime = fields.Float(compute='_compute_progress_hours', store=True)
|
||||
subtask_effective_hours = fields.Float("Sub-tasks Hours Spent", compute='_compute_subtask_effective_hours', store=True, help="Sum of actually spent hours on the subtask(s)")
|
||||
timesheet_ids = fields.One2many('account.analytic.line', 'task_id', 'Timesheets')
|
||||
|
||||
@@ -108,12 +109,14 @@ class Task(models.Model):
|
||||
for task in self:
|
||||
if (task.planned_hours > 0.0):
|
||||
task_total_hours = task.effective_hours + task.subtask_effective_hours
|
||||
task.overtime = max(task_total_hours - task.planned_hours, 0)
|
||||
if task_total_hours > task.planned_hours:
|
||||
task.progress = 100
|
||||
else:
|
||||
task.progress = round(100.0 * task_total_hours / task.planned_hours, 2)
|
||||
else:
|
||||
task.progress = 0.0
|
||||
task.overtime = 0
|
||||
|
||||
@api.depends('effective_hours', 'subtask_effective_hours', 'planned_hours')
|
||||
def _compute_remaining_hours(self):
|
||||
|
||||
@@ -281,6 +281,17 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="project_task_view_search" model="ir.ui.view">
|
||||
<field name="name">project.task.view.search.inherit.sale.timesheet.enterprise</field>
|
||||
<field name="model">project.task</field>
|
||||
<field name="inherit_id" ref="project.view_task_search_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//filter[@name='late']" position='after'>
|
||||
<filter string="Tasks in Overtime" name="overtime" domain="[('overtime', '>', 0)]"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Improve the task kanban in Project -->
|
||||
<record id="project_task_view_kanban" model="ir.ui.view">
|
||||
<field name="name">project.task.kanban.timer</field>
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
<filter string="Unassigned" name="unassigned" domain="[('user_id', '=', False)]"/>
|
||||
<separator/>
|
||||
<filter string="Starred" name="starred" domain="[('priority', 'in', [1, 2])]"/>
|
||||
<filter string="Late Tasks" name="late" domain="[('date_deadline', '<', context_today().strftime('%Y-%m-%d'))]"/>
|
||||
<separator/>
|
||||
<filter string="Unread Messages" name="message_needaction" domain="[('message_needaction', '=', True)]"/>
|
||||
<separator/>
|
||||
|
||||
@@ -5,6 +5,7 @@ import itertools
|
||||
import json
|
||||
|
||||
from odoo import fields, _, models
|
||||
from odoo.osv import expression
|
||||
from odoo.tools import float_round
|
||||
from odoo.tools.misc import get_lang
|
||||
|
||||
@@ -76,6 +77,11 @@ class Project(models.Model):
|
||||
# rates from non-invoiced timesheets that are linked to canceled so
|
||||
dashboard_values['rates']['canceled'] = float_round(100 * total_canceled_hours / (dashboard_total_hours or 1), precision_rounding=hour_rounding)
|
||||
|
||||
other_revenues = self.env['account.analytic.line'].read_group([
|
||||
('account_id', 'in', self.analytic_account_id.ids),
|
||||
('amount', '>=', 0),
|
||||
('project_id', '=', False)], ['amount'], [])[0].get('amount', 0)
|
||||
|
||||
# profitability, using profitability SQL report
|
||||
profit = dict.fromkeys(['invoiced', 'to_invoice', 'cost', 'expense_cost', 'expense_amount_untaxed_invoiced', 'total'], 0.0)
|
||||
profitability_raw_data = self.env['project.profitability.report'].read_group([('project_id', 'in', self.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_invoiced'], ['project_id'])
|
||||
@@ -85,6 +91,7 @@ class Project(models.Model):
|
||||
profit['cost'] += data.get('timesheet_cost', 0.0)
|
||||
profit['expense_cost'] += data.get('expense_cost', 0.0)
|
||||
profit['expense_amount_untaxed_invoiced'] += data.get('expense_amount_untaxed_invoiced', 0.0)
|
||||
profit['other_revenues'] = other_revenues or 0
|
||||
profit['total'] = sum([profit[item] for item in profit.keys()])
|
||||
dashboard_values['profit'] = profit
|
||||
|
||||
@@ -103,7 +110,8 @@ class Project(models.Model):
|
||||
employee_ids.extend(list(map(lambda x: x['employee_id'][0], aal_employee_ids)))
|
||||
|
||||
# Retrieve the employees for which the current user can see theirs timesheets
|
||||
employees = self.env['hr.employee'].sudo().browse(employee_ids).filtered_domain(self.env['account.analytic.line']._domain_employee_id())
|
||||
employee_domain = expression.AND([[('company_id', 'in', self.env.companies.ids)], self.env['account.analytic.line']._domain_employee_id()])
|
||||
employees = self.env['hr.employee'].sudo().browse(employee_ids).filtered_domain(employee_domain)
|
||||
repartition_domain = [('project_id', 'in', self.ids), ('employee_id', '!=', False), ('timesheet_invoice_type', '!=', False)] # force billable type
|
||||
# repartition data, without timesheet on cancelled so
|
||||
repartition_data = self.env['account.analytic.line'].read_group(repartition_domain + ['|', ('so_line', '=', False), ('so_line.state', '!=', 'cancel')], ['employee_id', 'timesheet_invoice_type', 'unit_amount'], ['employee_id', 'timesheet_invoice_type'], lazy=False)
|
||||
@@ -253,7 +261,7 @@ class Project(models.Model):
|
||||
month_index = fields.Date.from_string(date).month
|
||||
return babel.dates.get_month_names('abbreviated', locale=get_lang(self.env).code)[month_index]
|
||||
|
||||
header_names = [_('Name'), _('Before')] + [_to_short_month_name(date) for date in ts_months] + [_('Total'), _('Sold'), _('Remaining')]
|
||||
header_names = [_('Sales Order'), _('Before')] + [_to_short_month_name(date) for date in ts_months] + [_('Total'), _('Sold'), _('Remaining')]
|
||||
|
||||
result = []
|
||||
for name in header_names:
|
||||
@@ -411,6 +419,8 @@ class Project(models.Model):
|
||||
# if only one project, add it in the context as default value
|
||||
tasks_domain = [('project_id', 'in', self.ids)]
|
||||
tasks_context = self.env.context
|
||||
late_tasks_domain = [('project_id', 'in', self.ids), ('date_deadline', '<', fields.Date.to_string(fields.Date.today())), ('date_end', '=', False)]
|
||||
overtime_tasks_domain = [('project_id', 'in', self.ids), ('overtime', '>', 0), ('planned_hours', '>', 0)]
|
||||
|
||||
# filter out all the projects that have no tasks
|
||||
task_projects_ids = self.env['project.task'].read_group([('project_id', 'in', self.ids)], ['project_id'], ['project_id'])
|
||||
@@ -428,6 +438,26 @@ class Project(models.Model):
|
||||
context=tasks_context
|
||||
)
|
||||
})
|
||||
stat_buttons.append({
|
||||
'name': _("Late Tasks"),
|
||||
'count': self.env['project.task'].search_count(late_tasks_domain),
|
||||
'icon': 'fa fa-tasks',
|
||||
'action': _to_action_data(
|
||||
action=self.env.ref('project.action_view_task'),
|
||||
domain=late_tasks_domain,
|
||||
context=tasks_context,
|
||||
),
|
||||
})
|
||||
stat_buttons.append({
|
||||
'name': _("Tasks in Overtime"),
|
||||
'count': self.env['project.task'].search_count(overtime_tasks_domain),
|
||||
'icon': 'fa fa-tasks',
|
||||
'action': _to_action_data(
|
||||
action=self.env.ref('project.action_view_task'),
|
||||
domain=overtime_tasks_domain,
|
||||
context=tasks_context,
|
||||
),
|
||||
})
|
||||
|
||||
if self.env.user.has_group('sales_team.group_sale_salesman_all_leads'):
|
||||
# read all the sale orders linked to the projects' tasks
|
||||
|
||||
@@ -31,6 +31,8 @@ class ProfitabilityAnalysis(models.Model):
|
||||
amount_untaxed_invoiced = fields.Float("Untaxed Amount Invoiced", digits=(16, 2), readonly=True, group_operator="sum")
|
||||
expense_amount_untaxed_to_invoice = fields.Float("Untaxed Amount to Re-invoice", digits=(16, 2), readonly=True, group_operator="sum")
|
||||
expense_amount_untaxed_invoiced = fields.Float("Untaxed Re-invoiced Amount", digits=(16, 2), readonly=True, group_operator="sum")
|
||||
other_revenues = fields.Float("Other Revenues", digits=(16, 2), readonly=True, group_operator="sum",
|
||||
help="All revenues that are not from timesheets and that are linked to the analytic account of the project.")
|
||||
|
||||
def init(self):
|
||||
tools.drop_view_if_exists(self._cr, self._table)
|
||||
@@ -73,7 +75,8 @@ class ProfitabilityAnalysis(models.Model):
|
||||
END AS amount_untaxed_invoiced,
|
||||
COST_SUMMARY.timesheet_unit_amount AS timesheet_unit_amount,
|
||||
COST_SUMMARY.timesheet_cost AS timesheet_cost,
|
||||
COST_SUMMARY.expense_cost AS expense_cost
|
||||
COST_SUMMARY.expense_cost AS expense_cost,
|
||||
COST_SUMMARY.other_revenues AS other_revenues
|
||||
FROM project_project P
|
||||
JOIN res_company C ON C.id = P.company_id
|
||||
LEFT JOIN (
|
||||
@@ -84,6 +87,7 @@ class ProfitabilityAnalysis(models.Model):
|
||||
SUM(timesheet_unit_amount) AS timesheet_unit_amount,
|
||||
SUM(timesheet_cost) AS timesheet_cost,
|
||||
SUM(expense_cost) AS expense_cost,
|
||||
SUM(other_revenues) AS other_revenues,
|
||||
SUM(downpayment_invoiced) AS downpayment_invoiced
|
||||
FROM (
|
||||
SELECT
|
||||
@@ -92,6 +96,7 @@ class ProfitabilityAnalysis(models.Model):
|
||||
TS.so_line AS sale_line_id,
|
||||
SUM(TS.unit_amount) AS timesheet_unit_amount,
|
||||
SUM(TS.amount) AS timesheet_cost,
|
||||
0.0 AS other_revenues,
|
||||
0.0 AS expense_cost,
|
||||
0.0 AS downpayment_invoiced
|
||||
FROM account_analytic_line TS, project_project P
|
||||
@@ -106,6 +111,24 @@ class ProfitabilityAnalysis(models.Model):
|
||||
AAL.so_line AS sale_line_id,
|
||||
0.0 AS timesheet_unit_amount,
|
||||
0.0 AS timesheet_cost,
|
||||
SUM(AAL.amount) AS other_revenues,
|
||||
0.0 AS expense_cost,
|
||||
0.0 AS downpayment_invoiced
|
||||
FROM project_project P
|
||||
LEFT JOIN account_analytic_account AA ON P.analytic_account_id = AA.id
|
||||
LEFT JOIN account_analytic_line AAL ON AAL.account_id = AA.id
|
||||
WHERE AAL.amount > 0.0 AND AAL.project_id IS NULL AND P.active = 't' AND P.allow_timesheets = 't'
|
||||
GROUP BY P.id, AA.id, AAL.so_line
|
||||
|
||||
UNION
|
||||
|
||||
SELECT
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
AAL.so_line AS sale_line_id,
|
||||
0.0 AS timesheet_unit_amount,
|
||||
0.0 AS timesheet_cost,
|
||||
0.0 AS other_revenues,
|
||||
SUM(AAL.amount) AS expense_cost,
|
||||
0.0 AS downpayment_invoiced
|
||||
FROM project_project P
|
||||
@@ -122,6 +145,7 @@ class ProfitabilityAnalysis(models.Model):
|
||||
MY_SOLS.id AS sale_line_id,
|
||||
0.0 AS timesheet_unit_amount,
|
||||
0.0 AS timesheet_cost,
|
||||
0.0 AS other_revenues,
|
||||
0.0 AS expense_cost,
|
||||
CASE WHEN MY_SOLS.invoice_status = 'invoiced' THEN MY_SOLS.price_reduce ELSE 0.0 END AS downpayment_invoiced
|
||||
FROM project_project P
|
||||
@@ -139,6 +163,7 @@ class ProfitabilityAnalysis(models.Model):
|
||||
OLIS.id AS sale_line_id,
|
||||
0.0 AS timesheet_unit_amount,
|
||||
0.0 AS timesheet_cost,
|
||||
0.0 AS other_revenues,
|
||||
OLIS.price_reduce AS expense_cost,
|
||||
0.0 AS downpayment_invoiced
|
||||
FROM project_project P
|
||||
@@ -158,6 +183,7 @@ class ProfitabilityAnalysis(models.Model):
|
||||
SOL.id AS sale_line_id,
|
||||
0.0 AS timesheet_unit_amount,
|
||||
0.0 AS timesheet_cost,
|
||||
0.0 AS other_revenues,
|
||||
0.0 AS expense_cost,
|
||||
0.0 AS downpayment_invoiced
|
||||
FROM sale_order_line SOL
|
||||
@@ -172,6 +198,7 @@ class ProfitabilityAnalysis(models.Model):
|
||||
SOL.id AS sale_line_id,
|
||||
0.0 AS timesheet_unit_amount,
|
||||
0.0 AS timesheet_cost,
|
||||
0.0 AS other_revenues,
|
||||
0.0 AS expense_cost,
|
||||
0.0 AS downpayment_invoiced
|
||||
FROM sale_order_line SOL
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
<field name="amount_untaxed_to_invoice" type="measure"/>
|
||||
<field name="amount_untaxed_invoiced" type="measure"/>
|
||||
<field name="timesheet_cost" type="measure"/>
|
||||
<field name="other_revenues" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
odoo.define('sale_timesheet.project_overview', function (require) {
|
||||
"use strict";
|
||||
|
||||
var qweb = require('web.qweb');
|
||||
var viewRegistry = require('web.view_registry');
|
||||
|
||||
const Controller = qweb.Controller.extend({
|
||||
events: _.extend({}, qweb.Controller.prototype.events, {
|
||||
'click .project_overview_foldable': '_onFoldToggle',
|
||||
}),
|
||||
|
||||
_onFoldToggle(ev) {
|
||||
const {model, resId} = ev.target.dataset;
|
||||
const shouldOpen = ev.target.classList.contains('fa-caret-right');
|
||||
if (model === 'sale.order' && shouldOpen) {
|
||||
this._openSaleOrder(resId);
|
||||
}
|
||||
else if (model === 'sale.order.line' && shouldOpen) {
|
||||
this._openSaleOrderLine(resId);
|
||||
}
|
||||
else {
|
||||
const targetClass = `.${model.replace(/\./g, '_')}_${resId || 'None'}`;
|
||||
this.$(targetClass).hide();
|
||||
}
|
||||
$(ev.target).toggleClass('fa-caret-right');
|
||||
$(ev.target).toggleClass('fa-caret-down');
|
||||
},
|
||||
|
||||
_openSaleOrder(id) {
|
||||
id = id || 'None';
|
||||
const targetClass = `.sale_order_${id}`;
|
||||
this.$(`.o_timesheet_forecast_sale_order_line${targetClass}`).show();
|
||||
this.$(`.o_timesheet_forecast_hr_employee${targetClass}`).hide();
|
||||
this.$(`.o_timesheet_forecast_sale_order_line${targetClass} .fa`).removeClass('fa-caret-down');
|
||||
this.$(`.o_timesheet_forecast_sale_order_line${targetClass} .fa`).addClass('fa-caret-right');
|
||||
|
||||
},
|
||||
|
||||
_openSaleOrderLine(id) {
|
||||
id = id || 'None';
|
||||
const targetClass = `.sale_order_line_${id}`;
|
||||
this.$(`.o_timesheet_forecast_hr_employee${targetClass}`).show();
|
||||
},
|
||||
});
|
||||
|
||||
var ProjectOverview = qweb.View.extend({
|
||||
withSearchBar: true,
|
||||
searchMenuTypes: [],
|
||||
|
||||
config: _.extend({}, qweb.View.prototype.config, {
|
||||
Controller: Controller,
|
||||
}),
|
||||
});
|
||||
|
||||
viewRegistry.add('project_overview', ProjectOverview);
|
||||
});
|
||||
@@ -102,5 +102,5 @@ class TestSaleProject(TestReporting):
|
||||
self.assertEqual(float_compare(vals['dashboard']['profit']['invoiced'], project_invoiced, precision_rounding=rounding), 0, "The amount invoiced should be the one from the SO2 line, as we are in ordered quantity")
|
||||
self.assertEqual(float_compare(vals['dashboard']['profit']['cost'], project_timesheet_cost, precision_rounding=rounding), 0, "The amount cost should be the one from the SO2 line, as we are in ordered quantity")
|
||||
self.assertEqual(float_compare(vals['dashboard']['profit']['expense_cost'], expense.amount, precision_rounding=rounding), 0, "The amount expense-cost should be the one from the SO2 line, as we are in ordered quantity")
|
||||
self.assertEqual(float_compare(vals['dashboard']['profit']['total'], project_invoiced + project_timesheet_cost + expense.amount, precision_rounding=rounding), 0, "The total amount should be the one from the SO2 line, as we are in ordered quantity")
|
||||
self.assertEqual(float_compare(vals['dashboard']['profit']['total'], project_invoiced + project_timesheet_cost + expense.amount, precision_rounding=rounding), 1, "The total amount should be the one from the SO2 line, as we are in ordered quantity")
|
||||
self.assertEqual(float_compare(vals['repartition_employee_max'], 11.0, precision_rounding=rounding), 0, "The amount of repartition-employee-max should be the one from SO2 line")
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<template id="assets_backend" name="sale timesheet assets" inherit_id="web.assets_backend">
|
||||
<xpath expr="." position="inside">
|
||||
<link rel="stylesheet" type="text/scss" href="/sale_timesheet/static/src/scss/sale_timesheet.scss"/>
|
||||
<script type="text/javascript" src="/sale_timesheet/static/src/js/project_overview.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
@@ -12,6 +13,7 @@
|
||||
<field name="type">qweb</field>
|
||||
<field name="model">project.project</field>
|
||||
<field name="arch" type="xml">
|
||||
<qweb js_class="project_overview">
|
||||
<nav class="o_qweb_cp_buttons" t-if="actions">
|
||||
<button t-foreach="actions" t-as="action"
|
||||
type="action" class="btn btn-primary"
|
||||
@@ -27,7 +29,7 @@
|
||||
<div class="o_timesheet_plan_sale_timesheet">
|
||||
<div class="o_timesheet_plan_sale_timesheet_dashboard">
|
||||
|
||||
<div class="o_timesheet_plan_stat_buttons oe_button_box">
|
||||
<div class="o_timesheet_plan_stat_buttons oe_button_box o_not_full">
|
||||
<t t-foreach="stat_buttons" t-as="stat_button">
|
||||
<a class="btn oe_stat_button"
|
||||
type="action"
|
||||
@@ -152,6 +154,12 @@
|
||||
</td>
|
||||
<td>To invoice</td>
|
||||
</tr>
|
||||
<tr t-if="dashboard['profit']['other_revenues'] > 0">
|
||||
<td class="o_timesheet_plan_dashboard_cell">
|
||||
<t t-esc="dashboard['profit']['other_revenues']" t-options='{"widget": "monetary", "display_currency": currency}'/>
|
||||
</td>
|
||||
<td title="All revenues that are not from timesheets and that are linked to the analytic account of the project.">Other Revenues</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="o_timesheet_plan_dashboard_cell">
|
||||
<t t-esc="dashboard['profit']['cost']" t-options='{"widget": "monetary", "display_currency": currency}'/>
|
||||
@@ -289,15 +297,28 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<t t-set="row_is_milestone" t-value="False"/>
|
||||
<t t-set="current_order" t-value="False"/>
|
||||
<t t-set="current_order_line" t-value="False"/>
|
||||
<t t-foreach="timesheet_forecast_table['rows']" t-as="row">
|
||||
<t t-set="row_type" t-value="row[0].get('type')"/>
|
||||
<t t-if="row_type == 'sale_order_line'">
|
||||
<t t-set="row_is_milestone" t-value="row[0].get('is_milestone')"/>
|
||||
</t>
|
||||
<tr t-att-class="'o_timesheet_forecast_' + row_type">
|
||||
<t t-if="row_type == 'sale_order'">
|
||||
<t t-set="current_order" t-value="False"/>
|
||||
<t t-set="current_order_line" t-value="False"/>
|
||||
</t>
|
||||
<t t-if="row_type == 'sale_order_line'">
|
||||
<t t-set="current_order_line" t-value="False"/>
|
||||
</t>
|
||||
<t t-set="foldable" t-value="row_type in ('sale_order', 'sale_order_line')"/>
|
||||
<tr t-att-class="'o_timesheet_forecast_' + row_type + ' sale_order_' + str(current_order) + ' sale_order_line_' + str(current_order_line)"
|
||||
t-att-style="'display: none;' if row_type != 'sale_order' else ''">
|
||||
<t t-foreach="row" t-as="row_value">
|
||||
<td t-att-class="'o_right_bordered' if row_value_index in [5,10] else '' + ' text-center' if row_value_index != 0 else ''">
|
||||
<t t-if="row_value_index == 0">
|
||||
<span t-if="foldable" t-att-class="'fa fa-caret-right' + (' project_overview_foldable' if foldable else '')" style="cursor: pointer;"
|
||||
t-att-data-model="row[0].get('res_model')" t-att-data-res-id="row[0].get('res_id')"/>
|
||||
<t t-if="row_type == 'sale_order'">
|
||||
<a type="action" t-att-data-model="row_value['res_model']" t-att-data-res-id="row_value['res_id']" t-att-class="'o_timesheet_plan_redirect' if row_value['res_id'] else ''">
|
||||
<t t-esc="row_value.get('label')"/>
|
||||
@@ -333,6 +354,12 @@
|
||||
</td>
|
||||
</t>
|
||||
</tr>
|
||||
<t t-if="row_type == 'sale_order_line'">
|
||||
<t t-set="current_order_line" t-value="row[0].get('res_id')"/>
|
||||
</t>
|
||||
<t t-if="row_type == 'sale_order'">
|
||||
<t t-set="current_order" t-value="row[0].get('res_id')"/>
|
||||
</t>
|
||||
</t>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -344,6 +371,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</qweb>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import babel.dates
|
||||
import pytz
|
||||
from lxml import etree
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.osv.expression import AND
|
||||
@@ -202,9 +203,11 @@ class Base(models.AbstractModel):
|
||||
def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False):
|
||||
r = super().fields_view_get(view_id, view_type, toolbar, submenu)
|
||||
# avoid leaking the raw (un-rendered) template, also avoids bloating
|
||||
# the response payload for no reason
|
||||
# the response payload for no reason. Only send the root node,
|
||||
# to send attributes such as `js_class`.
|
||||
if r['type'] == 'qweb':
|
||||
r['arch'] = '<qweb/>'
|
||||
root = etree.fromstring(r['arch'])
|
||||
r['arch'] = etree.tostring(etree.Element('qweb', root.attrib))
|
||||
return r
|
||||
|
||||
@api.model
|
||||
|
||||
Reference in New Issue
Block a user