[IMP] project,*: sort the row displayed in profitability section

Before this commit, the order of rows shown in costs and revenues is not
fixed and depends on the order of the modules.

This commit adds a sequence on each row and then sort the rows to fix
the order of those rows.

task-2710808
This commit is contained in:
Xavier BOL (xbo)
2022-04-01 15:33:00 +02:00
committed by Laurent Stukkens (LTU)
parent 88d94c852a
commit e26bdb3282
11 changed files with 87 additions and 15 deletions
+8 -1
View File
@@ -734,13 +734,20 @@ class Project(models.Model):
'currency_id': self.currency_id.id,
}
if self._show_profitability():
panel_data['profitability_items'] = self._get_profitability_items()
profitability_items = self._get_profitability_items()
if self._get_profitability_sequence_per_invoice_type() and profitability_items and 'revenues' in profitability_items and 'costs' in profitability_items: # sort the data values
profitability_items['revenues']['data'] = sorted(profitability_items['revenues']['data'], key=lambda k: k['sequence'])
profitability_items['costs']['data'] = sorted(profitability_items['costs']['data'], key=lambda k: k['sequence'])
panel_data['profitability_items'] = profitability_items
panel_data['profitability_labels'] = self._get_profitability_labels()
return panel_data
def _get_profitability_labels(self):
return {}
def _get_profitability_sequence_per_invoice_type(self):
return {}
def _get_user_values(self):
return {
'is_project_user': self.user_has_groups('project.group_project_user'),
+6 -1
View File
@@ -66,6 +66,11 @@ class Project(models.Model):
labels['expenses'] = _lt('Expenses')
return labels
def _get_profitability_sequence_per_invoice_type(self):
sequence_per_invoice_type = super()._get_profitability_sequence_per_invoice_type()
sequence_per_invoice_type['expenses'] = 11
return sequence_per_invoice_type
def _get_expenses_profitability_items(self, with_action=True):
if not self.analytic_account_id:
return {}
@@ -82,7 +87,7 @@ class Project(models.Model):
expense_data = expenses_read_group[0]
section_id = 'expenses'
expense_profitability_items = {
'costs': {'id': section_id, 'billed': -expense_data['untaxed_amount'], 'to_bill': 0.0},
'costs': {'id': section_id, 'sequence': self._get_profitability_sequence_per_invoice_type()[section_id], 'billed': -expense_data['untaxed_amount'], 'to_bill': 0.0},
}
if can_see_expense:
expense_profitability_items['action'] = {'name': 'action_profitability_items', 'type': 'object', 'section': section_id, 'domain': json.dumps([('id', 'in', expense_data['ids'])])}
@@ -47,11 +47,15 @@ class TestProjectHrExpenseProfitability(TestProjectProfitabilityCommon, TestProj
self.project,
self.project_profitability_items_empty)
sequence_per_invoice_type = self.project._get_profitability_sequence_per_invoice_type()
self.assertIn('expenses', sequence_per_invoice_type)
expense_sequence = sequence_per_invoice_type['expenses']
self.assertDictEqual(
self.project._get_profitability_items(False),
{
'costs': {
'data': [{'id': 'expenses', 'to_bill': 0.0, 'billed': -expense.untaxed_amount}],
'data': [{'id': 'expenses', 'sequence': expense_sequence, 'to_bill': 0.0, 'billed': -expense.untaxed_amount}],
'total': {'to_bill': 0.0, 'billed': -expense.untaxed_amount},
},
'revenues': {'data': [], 'total': {'to_invoice': 0.0, 'invoiced': 0.0}},
+6
View File
@@ -42,6 +42,11 @@ class Project(models.Model):
labels['manufacturing_order'] = _lt('Manufacturing Orders')
return labels
def _get_profitability_sequence_per_invoice_type(self):
sequence_per_invoice_type = super()._get_profitability_sequence_per_invoice_type()
sequence_per_invoice_type['manufacturing_order'] = 10
return sequence_per_invoice_type
def _get_profitability_aal_domain(self):
return expression.AND([
super()._get_profitability_aal_domain(),
@@ -60,6 +65,7 @@ class Project(models.Model):
can_see_manufactoring_order = with_action and len(self) == 1 and self.user_has_groups('mrp.group_mrp_user')
mrp_costs = {
'id': mrp_category,
'sequence': self._get_profitability_sequence_per_invoice_type()[mrp_category],
'billed': sum([res['amount'] for res in mrp_aal_read_group]),
'to_bill': 0.0,
}
+6 -1
View File
@@ -94,6 +94,11 @@ class Project(models.Model):
labels['purchase_order'] = _lt('Purchase Orders')
return labels
def _get_profitability_sequence_per_invoice_type(self):
sequence_per_invoice_type = super()._get_profitability_sequence_per_invoice_type()
sequence_per_invoice_type['purchase_order'] = 9
return sequence_per_invoice_type
def _get_profitability_items(self, with_action=True):
profitability_items = super()._get_profitability_items(with_action)
if self.analytic_account_id:
@@ -112,7 +117,7 @@ class Project(models.Model):
purchase_order_line_ids.append(pol_read['id'])
costs = profitability_items['costs']
section_id = 'purchase_order'
purchase_order_costs = {'id': section_id, 'billed': amount_invoiced, 'to_bill': amount_to_invoice}
purchase_order_costs = {'id': section_id, 'sequence': self._get_profitability_sequence_per_invoice_type()[section_id], 'billed': amount_invoiced, 'to_bill': amount_to_invoice}
if with_action and purchase_order_line_ids and self.user_has_groups('purchase.group_purchase_user'):
action = {'name': 'action_profitability_items', 'type': 'object', 'section': section_id, 'domain': json.dumps([('id', 'in', purchase_order_line_ids)])}
if len(purchase_order_line_ids) == 1:
@@ -54,9 +54,11 @@ class Project(models.Model):
total_amount_expense_invoiced += res['untaxed_amount_invoiced']
reinvoice_expense_ids += expense_data_per_product_id[product_id]
section_id = 'expenses'
sequence = self._get_profitability_sequence_per_invoice_type()[section_id]
expense_data = {
'costs': {
'id': section_id,
'sequence': sequence,
'billed': -amount_billed,
'to_bill': 0.0,
},
@@ -64,6 +66,7 @@ class Project(models.Model):
if reinvoice_expense_ids:
expense_data['revenues'] = {
'id': section_id,
'sequence': sequence,
'invoiced': total_amount_expense_invoiced,
'to_invoice': total_amount_expense_to_invoice,
}
@@ -27,6 +27,9 @@ class TestProjectSaleExpenseProfitability(TestProjectProfitabilityCommon, TestPr
self.project_profitability_items_empty)
expense_profitability = self.project._get_expenses_profitability_items(False)
sequence_per_invoice_type = self.project._get_profitability_sequence_per_invoice_type()
self.assertIn('expenses', sequence_per_invoice_type)
expense_sequence = sequence_per_invoice_type['expenses']
self.assertDictEqual(
expense_profitability.get('revenues', {}),
@@ -34,7 +37,7 @@ class TestProjectSaleExpenseProfitability(TestProjectProfitabilityCommon, TestPr
)
self.assertDictEqual(
expense_profitability['costs'],
{'id': 'expenses', 'billed': -280.0, 'to_bill': 0.0},
{'id': 'expenses', 'sequence': expense_sequence, 'billed': -280.0, 'to_bill': 0.0},
)
expense_sheet.action_sheet_move_create()
@@ -59,11 +62,11 @@ class TestProjectSaleExpenseProfitability(TestProjectProfitabilityCommon, TestPr
expense_profitability = self.project._get_expenses_profitability_items(False)
self.assertDictEqual(
expense_profitability.get('revenues', {}),
{'id': 'expenses', 'invoiced': 0.0, 'to_invoice': expense_sol.untaxed_amount_to_invoice},
{'id': 'expenses', 'sequence': expense_sequence, 'invoiced': 0.0, 'to_invoice': expense_sol.untaxed_amount_to_invoice},
)
self.assertDictEqual(
expense_profitability['costs'],
{'id': 'expenses', 'billed': -280.0, 'to_bill': 0.0},
{'id': 'expenses', 'sequence': expense_sequence, 'billed': -280.0, 'to_bill': 0.0},
)
self.assertDictEqual(
@@ -92,7 +95,7 @@ class TestProjectSaleExpenseProfitability(TestProjectProfitabilityCommon, TestPr
expense_profitability = self.project._get_expenses_profitability_items(False)
self.assertDictEqual(
expense_profitability.get('revenues', {}),
{'id': 'expenses', 'invoiced': expense_sol.untaxed_amount_invoiced, 'to_invoice': 0.0},
{'id': 'expenses', 'sequence': expense_sequence, 'invoiced': expense_sol.untaxed_amount_invoiced, 'to_invoice': 0.0},
)
credit_note = invoice._reverse_moves()
@@ -101,7 +104,7 @@ class TestProjectSaleExpenseProfitability(TestProjectProfitabilityCommon, TestPr
expense_profitability = self.project._get_expenses_profitability_items(False)
self.assertDictEqual(
expense_profitability.get('revenues', {}),
{'id': 'expenses', 'invoiced': 0.0, 'to_invoice': expense_sol.untaxed_amount_to_invoice},
{'id': 'expenses', 'sequence': expense_sequence, 'invoiced': 0.0, 'to_invoice': expense_sol.untaxed_amount_to_invoice},
)
self.sale_order.action_cancel()
@@ -112,7 +115,7 @@ class TestProjectSaleExpenseProfitability(TestProjectProfitabilityCommon, TestPr
)
self.assertDictEqual(
expense_profitability['costs'],
{'id': 'expenses', 'billed': -280.0, 'to_bill': 0.0},
{'id': 'expenses', 'sequence': expense_sequence, 'billed': -280.0, 'to_bill': 0.0},
)
expense_sheet.refuse_sheet('Test Cancel Expense')
+9 -1
View File
@@ -299,6 +299,13 @@ class Project(models.Model):
'other_revenues': _lt('Material'),
}
def _get_profitability_sequence_per_invoice_type(self):
return {
**super()._get_profitability_sequence_per_invoice_type(),
'service_revenues': 6,
'other_revenues': 7,
}
def _get_service_policy_to_invoice_type(self):
return {
'ordered_prepaid': 'service_revenues',
@@ -372,8 +379,9 @@ class Project(models.Model):
if len(sol_ids) == 1:
action_params['res_id'] = sol_ids[0]
other_revenues['action'] = action_params
sequence_per_invoice_type = self._get_profitability_sequence_per_invoice_type()
return {
'data': [{'id': invoice_type, **vals} for invoice_type, vals in revenues_dict.items()],
'data': [{'id': invoice_type, 'sequence': sequence_per_invoice_type[invoice_type], **vals} for invoice_type, vals in revenues_dict.items()],
'total': {'to_invoice': total_to_invoice, 'invoiced': total_invoiced},
}
@@ -76,6 +76,8 @@ class TestSaleProjectProfitability(TestProjectProfitabilityCommon, TestSaleCommo
invoice_type,
['billable_manual', 'service_revenues'],
'invoice_type="billable_manual" if sale_timesheet is installed otherwise it is equal to "service_revenues"')
sequence_per_invoice_type = self.project._get_profitability_sequence_per_invoice_type()
self.assertIn('service_revenues', sequence_per_invoice_type)
self.assertDictEqual(
self.project._get_profitability_items(False),
{
@@ -84,6 +86,7 @@ class TestSaleProjectProfitability(TestProjectProfitabilityCommon, TestSaleCommo
{
# id should be equal to "billable_manual" if "sale_timesheet" module is installed otherwise "service_revenues"
'id': invoice_type,
'sequence': sequence_per_invoice_type[invoice_type],
'to_invoice': self.delivery_service_order_line.untaxed_amount_to_invoice,
'invoiced': self.delivery_service_order_line.untaxed_amount_invoiced,
},
@@ -128,6 +131,7 @@ class TestSaleProjectProfitability(TestProjectProfitabilityCommon, TestSaleCommo
'data': [
{
'id': invoice_type,
'sequence': sequence_per_invoice_type[invoice_type],
'to_invoice': 0.0,
'invoiced': self.delivery_service_order_line.untaxed_amount_invoiced,
},
@@ -169,11 +173,13 @@ class TestSaleProjectProfitability(TestProjectProfitabilityCommon, TestSaleCommo
'data': [
{
'id': invoice_type,
'sequence': sequence_per_invoice_type[invoice_type],
'to_invoice': sum(service_sols.mapped('untaxed_amount_to_invoice')),
'invoiced': sum(service_sols.mapped('untaxed_amount_invoiced')),
},
{
'id': 'other_revenues',
'sequence': sequence_per_invoice_type['other_revenues'],
'to_invoice': material_order_line.untaxed_amount_to_invoice,
'invoiced': material_order_line.untaxed_amount_invoiced,
},
@@ -203,11 +209,13 @@ class TestSaleProjectProfitability(TestProjectProfitabilityCommon, TestSaleCommo
'data': [
{
'id': invoice_type,
'sequence': sequence_per_invoice_type[invoice_type],
'to_invoice': sum(service_sols.mapped('untaxed_amount_to_invoice')),
'invoiced': manual_service_order_line.untaxed_amount_invoiced,
},
{
'id': 'other_revenues',
'sequence': sequence_per_invoice_type['other_revenues'],
'to_invoice': material_order_line.untaxed_amount_to_invoice,
'invoiced': material_order_line.untaxed_amount_invoiced,
},
+13 -1
View File
@@ -394,6 +394,16 @@ class Project(models.Model):
'non_billable': _lt('Timesheets (Non Billable)'),
}
def _get_profitability_sequence_per_invoice_type(self):
return {
**super()._get_profitability_sequence_per_invoice_type(),
'billable_fixed': 1,
'billable_time': 2,
'billable_milestones': 3,
'billable_manual': 4,
'non_billable': 5,
}
def _get_profitability_aal_domain(self):
domain = ['|', ('project_id', 'in', self.ids), ('so_line', 'in', self._fetch_sale_order_item_ids())]
return expression.AND([
@@ -451,6 +461,8 @@ class Project(models.Model):
action_params['res_id'] = record_ids[0]
return action_params
sequence_per_invoice_type = self._get_profitability_sequence_per_invoice_type()
def convert_dict_into_profitability_data(d, cost=True):
profitability_data = []
key1, key2 = ['to_bill', 'billed'] if cost else ['to_invoice', 'invoiced']
@@ -458,7 +470,7 @@ class Project(models.Model):
if not vals[key1] and not vals[key2]:
continue
record_ids = vals.pop('record_ids', [])
data = {'id': invoice_type, **vals}
data = {'id': invoice_type, 'sequence': sequence_per_invoice_type[invoice_type], **vals}
if record_ids:
if invoice_type not in ['other_costs', 'other_revenues'] and can_see_timesheets: # action to see the timesheets
action = get_timesheets_action(invoice_type, record_ids)
@@ -74,6 +74,12 @@ class TestSaleTimesheetProjectProfitability(TestCommonSaleTimesheet):
'unit_amount': 2.0,
})
sequence_per_invoice_type = self.project_task_rate._get_profitability_sequence_per_invoice_type()
self.assertIn('billable_time', sequence_per_invoice_type)
self.assertIn('billable_fixed', sequence_per_invoice_type)
self.assertIn('billable_milestones', sequence_per_invoice_type)
self.assertIn('billable_manual', sequence_per_invoice_type)
self.assertEqual(self.task.sale_line_id, delivery_service_order_line)
self.assertEqual((timesheet1 + timesheet2).so_line, delivery_service_order_line)
self.assertEqual(delivery_service_order_line.qty_delivered, 0.0, 'The service type is not timesheet but manual so the quantity delivered is not increased by the timesheets linked.')
@@ -88,6 +94,7 @@ class TestSaleTimesheetProjectProfitability(TestCommonSaleTimesheet):
'data': [
{
'id': 'billable_manual',
'sequence': sequence_per_invoice_type['billable_manual'],
'billed': (timesheet1.unit_amount + timesheet2.unit_amount) * -self.employee_user.timesheet_cost,
'to_bill': 0.0,
},
@@ -121,11 +128,13 @@ class TestSaleTimesheetProjectProfitability(TestCommonSaleTimesheet):
'data': [
{
'id': 'billable_manual',
'sequence': sequence_per_invoice_type['billable_manual'],
'billed': (timesheet1.unit_amount + timesheet2.unit_amount) * -self.employee_user.timesheet_cost,
'to_bill': 0.0,
},
{
'id': 'non_billable',
'sequence': sequence_per_invoice_type['non_billable'],
'billed': timesheet3.unit_amount * -self.employee_manager.timesheet_cost,
'to_bill': 0.0,
},
@@ -154,7 +163,7 @@ class TestSaleTimesheetProjectProfitability(TestCommonSaleTimesheet):
{
'revenues': {
'data': [
{'id': 'billable_time', 'to_invoice': delivery_timesheet_order_line.untaxed_amount_to_invoice, 'invoiced': 0.0},
{'id': 'billable_time', 'sequence': sequence_per_invoice_type['billable_time'], 'to_invoice': delivery_timesheet_order_line.untaxed_amount_to_invoice, 'invoiced': 0.0},
],
'total': {'to_invoice': delivery_timesheet_order_line.untaxed_amount_to_invoice, 'invoiced': 0.0},
},
@@ -162,11 +171,13 @@ class TestSaleTimesheetProjectProfitability(TestCommonSaleTimesheet):
'data': [
{
'id': 'billable_time',
'sequence': sequence_per_invoice_type['billable_time'],
'billed': (timesheet1.unit_amount + timesheet2.unit_amount) * -self.employee_user.timesheet_cost,
'to_bill': 0.0,
},
{
'id': 'non_billable',
'sequence': sequence_per_invoice_type['non_billable'],
'billed': timesheet3.unit_amount * -self.employee_manager.timesheet_cost,
'to_bill': 0.0,
},
@@ -200,14 +211,14 @@ class TestSaleTimesheetProjectProfitability(TestCommonSaleTimesheet):
self.assertFalse([data for data in profitability_items['revenues']['data'] if data['id'] == 'billable_milestones'])
self.assertDictEqual(
[data for data in profitability_items['costs']['data'] if data['id'] == 'billable_milestones'][0],
{'id': 'billable_milestones', 'to_bill': 0.0, 'billed': task2_timesheet.amount},
{'id': 'billable_milestones', 'sequence': sequence_per_invoice_type['billable_milestones'], 'to_bill': 0.0, 'billed': task2_timesheet.amount},
)
milestone_order_line.qty_delivered = 1
profitability_items = self.project_task_rate._get_profitability_items(False)
self.assertDictEqual(
[data for data in profitability_items['revenues']['data'] if data['id'] == 'billable_milestones'][0],
{'id': 'billable_milestones', 'to_invoice': milestone_order_line.untaxed_amount_to_invoice, 'invoiced': 0.0},
{'id': 'billable_milestones', 'sequence': sequence_per_invoice_type['billable_milestones'], 'to_invoice': milestone_order_line.untaxed_amount_to_invoice, 'invoiced': 0.0},
)
task2_timesheet.unlink()
profitability_items = self.project_task_rate._get_profitability_items(False)