[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:
committed by
Laurent Stukkens (LTU)
parent
88d94c852a
commit
e26bdb3282
@@ -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'),
|
||||
|
||||
@@ -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}},
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user