[IMP] sale_timesheet: small improvements for the reporting

Purpose
=======

Small improvements for the reporting in Project Overview and allow to group by date in Project Costs and Revenues

Specifications
==============

In Project Overview:
- unfold the details of SO by default
- do not display caret if it is not foldable

In Project Costs and Revenues, the date is retrieved for each account_analytic_line and therefore the values are not summed anymore in the SQL request.
Also fix the default filter "Costs and Revenues" that wasn't applied at the opening of the report.

closes odoo/odoo#48172

Related: odoo/enterprise#9415
Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com>
This commit is contained in:
Anh Thao Pham (pta)
2020-06-25 08:34:23 +00:00
committed by Yannick Tivisse
parent c5e1af7a35
commit e2d1d445a9
5 changed files with 191 additions and 75 deletions
@@ -7,9 +7,8 @@
<field name="user_id" eval="False"/>
<field name="is_default" eval="True"/>
<field name="context">{
'group_by': ['project_id', 'sale_line_id'],
'pivot_measures': ['amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_cost', 'timesheet_unit_amount'],
'pivot_column_groupby': [], 'pivot_row_groupby': []
'group_by': ['project_id', 'sale_line_id'],
'pivot_measures': ['amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_cost', 'timesheet_unit_amount']
}</field>
</record>
@@ -243,9 +243,11 @@ class Project(models.Model):
timesheet_forecast_table_rows.append(sale_order_row)
for sale_line_row_key, sale_line_row in rows_sale_line.items():
if sale_order_id == sale_line_row_key[0]:
sale_order_row[0]['has_children'] = True
timesheet_forecast_table_rows.append(sale_line_row)
for employee_row_key, employee_row in rows_employee.items():
if sale_order_id == employee_row_key[0] and sale_line_row_key[1] == employee_row_key[1] and employee_row_key[2] in employees.ids:
sale_line_row[0]['has_children'] = True
timesheet_forecast_table_rows.append(employee_row)
# complete table data
@@ -17,6 +17,7 @@ class ProfitabilityAnalysis(models.Model):
company_id = fields.Many2one('res.company', string='Project Company', readonly=True)
user_id = fields.Many2one('res.users', string='Project Manager', readonly=True)
partner_id = fields.Many2one('res.partner', string='Customer', readonly=True)
line_date = fields.Date("Date", readonly=True)
# cost
timesheet_unit_amount = fields.Float("Timesheet Duration", digits=(16, 2), readonly=True, group_operator="sum")
timesheet_cost = fields.Float("Timesheet Cost", digits=(16, 2), readonly=True, group_operator="sum")
@@ -27,7 +28,7 @@ class ProfitabilityAnalysis(models.Model):
sale_order_id = fields.Many2one('sale.order', string='Sale Order', readonly=True)
product_id = fields.Many2one('product.product', string='Product', readonly=True)
amount_untaxed_to_invoice = fields.Float("Untaxed Amount To Invoice", digits=(16, 2), readonly=True, group_operator="sum")
amount_untaxed_to_invoice = fields.Float("Untaxed Amount to Invoice", digits=(16, 2), readonly=True, group_operator="sum")
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 Amount Re-invoiced", digits=(16, 2), readonly=True, group_operator="sum")
@@ -60,6 +61,7 @@ class ProfitabilityAnalysis(models.Model):
sub.timesheet_cost as timesheet_cost,
sub.expense_cost as expense_cost,
sub.other_revenues as other_revenues,
sub.line_date as line_date,
(sub.expense_amount_untaxed_to_invoice + sub.expense_amount_untaxed_invoiced + sub.amount_untaxed_to_invoice +
sub.amount_untaxed_invoiced + sub.other_revenues + sub.timesheet_cost + sub.expense_cost)
as margin
@@ -77,35 +79,20 @@ class ProfitabilityAnalysis(models.Model):
S.date_order AS order_confirmation_date,
SOL.product_id AS product_id,
SOL.qty_delivered_method AS sale_qty_delivered_method,
CASE
WHEN SOL.qty_delivered_method = 'analytic' THEN (SOL.untaxed_amount_to_invoice / CASE COALESCE(S.currency_rate, 0) WHEN 0 THEN 1.0 ELSE S.currency_rate END)
ELSE 0.0
END AS expense_amount_untaxed_to_invoice,
CASE
WHEN SOL.qty_delivered_method = 'analytic' AND SOL.invoice_status != 'no'
THEN
CASE
WHEN T.expense_policy = 'sales_price'
THEN (SOL.price_reduce / CASE COALESCE(S.currency_rate, 0) WHEN 0 THEN 1.0 ELSE S.currency_rate END) * SOL.qty_invoiced
ELSE -COST_SUMMARY.expense_cost
END
ELSE 0.0
END AS expense_amount_untaxed_invoiced,
CASE
WHEN SOL.qty_delivered_method IN ('timesheet', 'manual') THEN (SOL.untaxed_amount_to_invoice / CASE COALESCE(S.currency_rate, 0) WHEN 0 THEN 1.0 ELSE S.currency_rate END)
ELSE 0.0
END AS amount_untaxed_to_invoice,
CASE
WHEN SOL.qty_delivered_method IN ('timesheet', 'manual') THEN (COALESCE(SOL.untaxed_amount_invoiced, COST_SUMMARY.downpayment_invoiced) / CASE COALESCE(S.currency_rate, 0) WHEN 0 THEN 1.0 ELSE S.currency_rate END)
ELSE 0.0
END AS amount_untaxed_invoiced,
COST_SUMMARY.expense_amount_untaxed_to_invoice AS expense_amount_untaxed_to_invoice,
COST_SUMMARY.expense_amount_untaxed_invoiced AS expense_amount_untaxed_invoiced,
COST_SUMMARY.amount_untaxed_to_invoice AS amount_untaxed_to_invoice,
COST_SUMMARY.amount_untaxed_invoiced 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.other_revenues AS other_revenues
COST_SUMMARY.other_revenues AS other_revenues,
COST_SUMMARY.line_date::date AS line_date
FROM project_project P
JOIN res_company C ON C.id = P.company_id
LEFT JOIN (
-- Each costs and revenues will be retrieved individually by sub-requests
-- This is required to able to get the date
SELECT
project_id,
analytic_account_id,
@@ -114,40 +101,56 @@ class ProfitabilityAnalysis(models.Model):
SUM(timesheet_cost) AS timesheet_cost,
SUM(expense_cost) AS expense_cost,
SUM(other_revenues) AS other_revenues,
SUM(downpayment_invoiced) AS downpayment_invoiced
SUM(downpayment_invoiced) AS downpayment_invoiced,
SUM(expense_amount_untaxed_to_invoice) AS expense_amount_untaxed_to_invoice,
SUM(expense_amount_untaxed_invoiced) AS expense_amount_untaxed_invoiced,
SUM(amount_untaxed_to_invoice) AS amount_untaxed_to_invoice,
SUM(amount_untaxed_invoiced) AS amount_untaxed_invoiced,
line_date AS line_date
FROM (
-- Get the timesheet costs
SELECT
P.id AS project_id,
P.analytic_account_id AS analytic_account_id,
TS.so_line AS sale_line_id,
SUM(TS.unit_amount) AS timesheet_unit_amount,
SUM(TS.amount) AS timesheet_cost,
TS.unit_amount AS timesheet_unit_amount,
TS.amount AS timesheet_cost,
0.0 AS other_revenues,
0.0 AS expense_cost,
0.0 AS downpayment_invoiced
0.0 AS downpayment_invoiced,
0.0 AS expense_amount_untaxed_to_invoice,
0.0 AS expense_amount_untaxed_invoiced,
0.0 AS amount_untaxed_to_invoice,
0.0 AS amount_untaxed_invoiced,
TS.date AS line_date
FROM account_analytic_line TS, project_project P
WHERE TS.project_id IS NOT NULL AND P.id = TS.project_id AND P.active = 't' AND P.allow_timesheets = 't'
GROUP BY P.id, TS.so_line
UNION
UNION ALL
-- Get the other revenues
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,
SUM(AAL.amount) AS other_revenues,
AAL.amount AS other_revenues,
0.0 AS expense_cost,
0.0 AS downpayment_invoiced
0.0 AS downpayment_invoiced,
0.0 AS expense_amount_untaxed_to_invoice,
0.0 AS expense_amount_untaxed_invoiced,
0.0 AS amount_untaxed_to_invoice,
0.0 AS amount_untaxed_invoiced,
AAL.date AS line_date
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
UNION ALL
-- Get the expense costs from account analytic line
SELECT
P.id AS project_id,
P.analytic_account_id AS analytic_account_id,
@@ -155,16 +158,21 @@ class ProfitabilityAnalysis(models.Model):
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
AAL.amount AS expense_cost,
0.0 AS downpayment_invoiced,
0.0 AS expense_amount_untaxed_to_invoice,
0.0 AS expense_amount_untaxed_invoiced,
0.0 AS amount_untaxed_to_invoice,
0.0 AS amount_untaxed_invoiced,
AAL.date AS line_date
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
UNION ALL
-- Get the invoiced downpayments
SELECT
P.id AS project_id,
P.analytic_account_id AS analytic_account_id,
@@ -173,16 +181,21 @@ class ProfitabilityAnalysis(models.Model):
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
CASE WHEN MY_SOLS.invoice_status = 'invoiced' THEN MY_SOLS.price_reduce ELSE 0.0 END AS downpayment_invoiced,
0.0 AS expense_amount_untaxed_to_invoice,
0.0 AS expense_amount_untaxed_invoiced,
0.0 AS amount_untaxed_to_invoice,
0.0 AS amount_untaxed_invoiced,
MY_S.date_order AS line_date
FROM project_project P
LEFT JOIN sale_order_line MY_SOL ON P.sale_line_id = MY_SOL.id
LEFT JOIN sale_order MY_S ON MY_SOL.order_id = MY_S.id
LEFT JOIN sale_order_line MY_SOLS ON MY_SOLS.order_id = MY_S.id
WHERE MY_SOLS.is_downpayment = 't'
GROUP BY P.id, MY_SOLS.id
UNION
UNION ALL
-- Get the expense costs from sale order line
SELECT
P.id AS project_id,
P.analytic_account_id AS analytic_account_id,
@@ -191,7 +204,12 @@ class ProfitabilityAnalysis(models.Model):
0.0 AS timesheet_cost,
0.0 AS other_revenues,
OLIS.price_reduce AS expense_cost,
0.0 AS downpayment_invoiced
0.0 AS downpayment_invoiced,
0.0 AS expense_amount_untaxed_to_invoice,
0.0 AS expense_amount_untaxed_invoiced,
0.0 AS amount_untaxed_to_invoice,
0.0 AS amount_untaxed_invoiced,
ANLI.date AS line_date
FROM project_project P
LEFT JOIN account_analytic_account ANAC ON P.analytic_account_id = ANAC.id
LEFT JOIN account_analytic_line ANLI ON ANAC.id = ANLI.account_id
@@ -199,45 +217,141 @@ class ProfitabilityAnalysis(models.Model):
LEFT JOIN sale_order ORD ON OLI.order_id = ORD.id
LEFT JOIN sale_order_line OLIS ON ORD.id = OLIS.order_id
WHERE OLIS.product_id = ANLI.product_id AND OLIS.is_downpayment = 't' AND ANLI.amount < 0.0 AND ANLI.project_id IS NULL AND P.active = 't' AND P.allow_timesheets = 't'
GROUP BY P.id, OLIS.id
UNION
UNION ALL
-- Get the following values: expense amount untaxed to invoice/invoiced, amount untaxed to invoice/invoiced
-- These values have to be computed from all the records retrieved just above but grouped by project and sale order line
SELECT
P.id AS project_id,
P.analytic_account_id AS analytic_account_id,
SOL.id AS sale_line_id,
AMOUNT_UNTAXED.project_id AS project_id,
AMOUNT_UNTAXED.analytic_account_id AS analytic_account_id,
AMOUNT_UNTAXED.sale_line_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
INNER JOIN project_project P ON SOL.project_id = P.id
WHERE P.active = 't' AND P.allow_timesheets = 't'
0.0 AS downpayment_invoiced,
CASE
WHEN SOL.qty_delivered_method = 'analytic' THEN (SOL.untaxed_amount_to_invoice / CASE COALESCE(S.currency_rate, 0) WHEN 0 THEN 1.0 ELSE S.currency_rate END)
ELSE 0.0
END AS expense_amount_untaxed_to_invoice,
CASE
WHEN SOL.qty_delivered_method = 'analytic' AND SOL.invoice_status != 'no'
THEN
CASE
WHEN T.expense_policy = 'sales_price'
THEN (SOL.price_reduce / CASE COALESCE(S.currency_rate, 0) WHEN 0 THEN 1.0 ELSE S.currency_rate END) * SOL.qty_invoiced
ELSE -AMOUNT_UNTAXED.expense_cost
END
ELSE 0.0
END AS expense_amount_untaxed_invoiced,
CASE
WHEN SOL.qty_delivered_method IN ('timesheet', 'manual') THEN (SOL.untaxed_amount_to_invoice / CASE COALESCE(S.currency_rate, 0) WHEN 0 THEN 1.0 ELSE S.currency_rate END)
ELSE 0.0
END AS amount_untaxed_to_invoice,
CASE
WHEN SOL.qty_delivered_method IN ('timesheet', 'manual') THEN (COALESCE(SOL.untaxed_amount_invoiced, AMOUNT_UNTAXED.downpayment_invoiced) / CASE COALESCE(S.currency_rate, 0) WHEN 0 THEN 1.0 ELSE S.currency_rate END)
ELSE 0.0
END AS amount_untaxed_invoiced,
S.date_order AS line_date
FROM project_project P
JOIN res_company C ON C.id = P.company_id
LEFT JOIN (
SELECT
P.id AS project_id,
P.analytic_account_id AS analytic_account_id,
AAL.so_line AS sale_line_id,
0.0 AS expense_cost,
0.0 AS downpayment_invoiced
FROM account_analytic_line AAL, project_project P
WHERE AAL.project_id IS NOT NULL AND P.id = AAL.project_id AND P.active = 't'
GROUP BY P.id, AAL.so_line
UNION
UNION
SELECT
P.id AS project_id,
P.analytic_account_id AS analytic_account_id,
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
INNER JOIN project_task T ON SOL.task_id = T.id
INNER JOIN project_project P ON P.id = T.project_id
WHERE P.active = 't' AND P.allow_timesheets = 't'
SELECT
P.id AS project_id,
P.analytic_account_id AS analytic_account_id,
AAL.so_line AS sale_line_id,
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,
SUM(AAL.amount) 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,
MY_SOLS.id AS sale_line_id,
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
LEFT JOIN sale_order_line MY_SOL ON P.sale_line_id = MY_SOL.id
LEFT JOIN sale_order MY_S ON MY_SOL.order_id = MY_S.id
LEFT JOIN sale_order_line MY_SOLS ON MY_SOLS.order_id = MY_S.id
WHERE MY_SOLS.is_downpayment = 't'
GROUP BY P.id, MY_SOLS.id
UNION
SELECT
P.id AS project_id,
P.analytic_account_id AS analytic_account_id,
OLIS.id AS sale_line_id,
OLIS.price_reduce AS expense_cost,
0.0 AS downpayment_invoiced
FROM project_project P
LEFT JOIN account_analytic_account ANAC ON P.analytic_account_id = ANAC.id
LEFT JOIN account_analytic_line ANLI ON ANAC.id = ANLI.account_id
LEFT JOIN sale_order_line OLI ON P.sale_line_id = OLI.id
LEFT JOIN sale_order ORD ON OLI.order_id = ORD.id
LEFT JOIN sale_order_line OLIS ON ORD.id = OLIS.order_id
WHERE OLIS.product_id = ANLI.product_id AND OLIS.is_downpayment = 't' AND ANLI.amount < 0.0 AND ANLI.project_id IS NULL AND P.active = 't' AND P.allow_timesheets = 't'
GROUP BY P.id, OLIS.id
UNION
SELECT
P.id AS project_id,
P.analytic_account_id AS analytic_account_id,
SOL.id AS sale_line_id,
0.0 AS expense_cost,
0.0 AS downpayment_invoiced
FROM sale_order_line SOL
INNER JOIN project_project P ON SOL.project_id = P.id
WHERE P.active = 't' AND P.allow_timesheets = 't'
UNION
SELECT
P.id AS project_id,
P.analytic_account_id AS analytic_account_id,
SOL.id AS sale_line_id,
0.0 AS expense_cost,
0.0 AS downpayment_invoiced
FROM sale_order_line SOL
INNER JOIN project_task T ON SOL.task_id = T.id
INNER JOIN project_project P ON P.id = T.project_id
WHERE P.active = 't' AND P.allow_timesheets = 't'
) AMOUNT_UNTAXED ON AMOUNT_UNTAXED.project_id = P.id
LEFT JOIN sale_order_line SOL ON AMOUNT_UNTAXED.sale_line_id = SOL.id
LEFT JOIN sale_order S ON SOL.order_id = S.id
LEFT JOIN product_product PP on (SOL.product_id = PP.id)
LEFT JOIN product_template T on (PP.product_tmpl_id = T.id)
WHERE P.active = 't' AND P.analytic_account_id IS NOT NULL
) SUB_COST_SUMMARY
GROUP BY project_id, analytic_account_id, sale_line_id
GROUP BY project_id, analytic_account_id, sale_line_id, line_date
) COST_SUMMARY ON COST_SUMMARY.project_id = P.id
LEFT JOIN sale_order_line SOL ON COST_SUMMARY.sale_line_id = SOL.id
LEFT JOIN sale_order S ON SOL.order_id = S.id
LEFT JOIN product_product PP on (SOL.product_id=PP.id)
LEFT JOIN product_template T on (PP.product_tmpl_id=T.id)
WHERE P.active = 't' AND P.analytic_account_id IS NOT NULL
) AS sub
)
@@ -45,6 +45,7 @@
<filter string="Project Manager" name="group_by_user_id" context="{'group_by':'user_id'}"/>
<filter string="Customer" name="group_by_partner_id" context="{'group_by':'partner_id'}"/>
<filter string="Company" name="group_by_company" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
<filter string="Date" name="group_by_line_date" context="{'group_by':'line_date'}"/>
</group>
</search>
</field>
@@ -339,14 +339,14 @@
<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')"/>
<t t-set="foldable" t-value="row[0].get('has_children')"/>
<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-att-style="'display: none;' if row_type not in ('sale_order', 'sale_order_line') 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')"/>
<span t-if="foldable" t-att-class="('fa fa-caret-down' if row_type == 'sale_order' else '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')"/>