[FIX] (sale_)project(_purchase): proj.prof. w/ AA distr. for inv/bills

Current behaviour:
The project profitability panel doesn't take into account the AA
distribution of the project's AA for solo invoices and bills.

Expected behaviour:
It should take the distribution set into account.

Steps to reproduce:
- Install Accounting, Sales, Project, Purchase
- Settings > Activate AA
- Create a new project with a new AA for it.
- Create a new bill or invoice, on the line set the 2 AA, one with
  the project's AA with some X%, and the other AA is irrelevant (the
  sum of all AA on the line should be 100%)
- Confirm the invoice/bill
- Go to the project profitability report (project updates), see that
  we have 100% of the line value that contributed, when it should have.

Reason for the problem:
The contribution of the AA was not taken into account when computing
the project profitability for solo invoices/bills (invoices/bills
w/o SO/PO respectively)

Fix:
Additionally get the analytic_distribution when fetch the
account_move_lines to compute the project's profitability.

Affected versions:
- 16.0
- saas-16.1
- saas-16.2
- master

opw-3246199

closes odoo/odoo#117163

X-original-commit: c3c66d05d039514c9dd02ebdede20a3dff757728
Signed-off-by: Xavier Bol (xbo) <xbo@odoo.com>
Signed-off-by: Piryns Victor (pivi) <pivi@odoo.com>
This commit is contained in:
Victor Piryns (pivi)
2023-03-30 16:23:04 +02:00
parent 406ec9dcdf
commit 1adaaba90e
4 changed files with 56 additions and 44 deletions
@@ -177,7 +177,7 @@ class Project(models.Model):
# account_move_line__move_id is the alias of the joined table account_move in the query
# we can use it, because of the "move_id.move_type" clause in the domain of the query, which generates the join
# this is faster than a search_read followed by a browse on the move_id to retrieve the move_type of each account.move.line
query_string, query_param = query.select('price_subtotal', 'parent_state', 'account_move_line.currency_id', 'account_move_line__move_id.move_type')
query_string, query_param = query.select('price_subtotal', 'parent_state', 'account_move_line.currency_id', 'account_move_line.analytic_distribution', 'account_move_line__move_id.move_type')
self._cr.execute(query_string, query_param)
bills_move_line_read = self._cr.dictfetchall()
if bills_move_line_read:
@@ -190,16 +190,17 @@ class Project(models.Model):
amount_invoiced = amount_to_invoice = 0.0
for moves_read in bills_move_line_read:
price_subtotal = self.analytic_account_id.currency_id.round(moves_read['price_subtotal'] * conversion_rates[moves_read['currency_id']])
analytic_contribution = moves_read['analytic_distribution'][str(self.analytic_account_id.id)] / 100.
if moves_read['parent_state'] == 'draft':
if moves_read['move_type'] == 'in_invoice':
amount_to_invoice -= price_subtotal
amount_to_invoice -= price_subtotal * analytic_contribution
else: # moves_read['move_type'] == 'in_refund'
amount_to_invoice += price_subtotal
amount_to_invoice += price_subtotal * analytic_contribution
else: # moves_read['parent_state'] == 'posted'
if moves_read['move_type'] == 'in_invoice':
amount_invoiced -= price_subtotal
amount_invoiced -= price_subtotal * analytic_contribution
else: # moves_read['move_type'] == 'in_refund'
amount_invoiced += price_subtotal
amount_invoiced += price_subtotal * analytic_contribution
# don't display the section if the final values are both 0 (bill -> vendor credit)
if amount_invoiced != 0 or amount_to_invoice != 0:
costs = profitability_items['costs']
@@ -17,7 +17,12 @@ class TestProjectPurchaseProfitability(TestProjectProfitabilityCommon, TestPurch
"""
A bill that has an AAL on one of its line should be taken into account
for the profitability of the project.
The contribution of the line should only be dependent
on the project's analytic account % that was set on the line
"""
# a custom analytic contribution (number between 1 -> 100 included)
analytic_distribution = 42
analytic_contribution = analytic_distribution / 100.
# create a bill_1 with the AAL
bill_1 = self.env['account.move'].create({
"name": "Bill_1 name",
@@ -26,7 +31,7 @@ class TestProjectPurchaseProfitability(TestProjectProfitabilityCommon, TestPurch
"partner_id": self.partner.id,
"invoice_date": datetime.today(),
"invoice_line_ids": [Command.create({
"analytic_distribution": {self.analytic_account.id: 100},
"analytic_distribution": {self.analytic_account.id: analytic_distribution},
"product_id": self.product_a.id,
"quantity": 1,
"product_uom_id": self.product_a.uom_id.id,
@@ -41,10 +46,10 @@ class TestProjectPurchaseProfitability(TestProjectProfitabilityCommon, TestPurch
'data': [{
'id': 'other_purchase_costs',
'sequence': self.project._get_profitability_sequence_per_invoice_type()['other_purchase_costs'],
'to_bill': -self.product_a.standard_price,
'to_bill': -self.product_a.standard_price * analytic_contribution,
'billed': 0.0,
}],
'total': {'to_bill': -self.product_a.standard_price, 'billed': 0.0},
'total': {'to_bill': -self.product_a.standard_price * analytic_contribution, 'billed': 0.0},
},
)
# post bill_1
@@ -57,9 +62,9 @@ class TestProjectPurchaseProfitability(TestProjectProfitabilityCommon, TestPurch
'id': 'other_purchase_costs',
'sequence': self.project._get_profitability_sequence_per_invoice_type()['other_purchase_costs'],
'to_bill': 0.0,
'billed': -self.product_a.standard_price,
'billed': -self.product_a.standard_price * analytic_contribution,
}],
'total': {'to_bill': 0.0, 'billed': -self.product_a.standard_price},
'total': {'to_bill': 0.0, 'billed': -self.product_a.standard_price * analytic_contribution},
},
)
# create another bill, with 2 lines, 2 diff products, the second line has 2 as quantity
@@ -70,14 +75,14 @@ class TestProjectPurchaseProfitability(TestProjectProfitabilityCommon, TestPurch
"partner_id": self.partner.id,
"invoice_date": datetime.today(),
"invoice_line_ids": [Command.create({
"analytic_distribution": {self.analytic_account.id: 100},
"analytic_distribution": {self.analytic_account.id: analytic_distribution},
"product_id": self.product_a.id,
"quantity": 1,
"product_uom_id": self.product_a.uom_id.id,
"price_unit": self.product_a.standard_price,
"currency_id": self.env.company.currency_id.id,
}), Command.create({
"analytic_distribution": {self.analytic_account.id: 100},
"analytic_distribution": {self.analytic_account.id: analytic_distribution},
"product_id": self.product_b.id,
"quantity": 2,
"product_uom_id": self.product_b.uom_id.id,
@@ -92,12 +97,12 @@ class TestProjectPurchaseProfitability(TestProjectProfitabilityCommon, TestPurch
'data': [{
'id': 'other_purchase_costs',
'sequence': self.project._get_profitability_sequence_per_invoice_type()['other_purchase_costs'],
'to_bill': -(self.product_a.standard_price + 2 * self.product_b.standard_price),
'billed': -self.product_a.standard_price,
'to_bill': -(self.product_a.standard_price + 2 * self.product_b.standard_price) * analytic_contribution,
'billed': -self.product_a.standard_price * analytic_contribution,
}],
'total': {
'to_bill': -(self.product_a.standard_price + 2 * self.product_b.standard_price),
'billed': -self.product_a.standard_price,
'to_bill': -(self.product_a.standard_price + 2 * self.product_b.standard_price) * analytic_contribution,
'billed': -self.product_a.standard_price * analytic_contribution,
},
},
)
@@ -111,11 +116,11 @@ class TestProjectPurchaseProfitability(TestProjectProfitabilityCommon, TestPurch
'id': 'other_purchase_costs',
'sequence': self.project._get_profitability_sequence_per_invoice_type()['other_purchase_costs'],
'to_bill': 0.0,
'billed': -2 * (self.product_a.standard_price + self.product_b.standard_price),
'billed': -2 * (self.product_a.standard_price + self.product_b.standard_price) * analytic_contribution,
}],
'total': {
'to_bill': 0.0,
'billed': -2 * (self.product_a.standard_price + self.product_b.standard_price),
'billed': -2 * (self.product_a.standard_price + self.product_b.standard_price) * analytic_contribution,
},
},
)
@@ -124,7 +129,7 @@ class TestProjectPurchaseProfitability(TestProjectProfitabilityCommon, TestPurch
"name": "A purchase order",
"partner_id": self.partner_a.id,
"order_line": [Command.create({
"analytic_distribution": {self.analytic_account.id: 100},
"analytic_distribution": {self.analytic_account.id: analytic_distribution},
"product_id": self.product_order.id,
"product_qty": 1,
"price_unit": self.product_order.standard_price,
@@ -142,17 +147,17 @@ class TestProjectPurchaseProfitability(TestProjectProfitabilityCommon, TestPurch
'data': [{
'id': 'purchase_order',
'sequence': self.project._get_profitability_sequence_per_invoice_type()['purchase_order'],
'to_bill': -self.product_order.standard_price,
'to_bill': -self.product_order.standard_price * analytic_contribution,
'billed': 0.0,
}, {
'id': 'other_purchase_costs',
'sequence': self.project._get_profitability_sequence_per_invoice_type()['other_purchase_costs'],
'to_bill': 0.0,
'billed': -2 * (self.product_a.standard_price + self.product_b.standard_price),
'billed': -2 * (self.product_a.standard_price + self.product_b.standard_price) * analytic_contribution,
}],
'total': {
'to_bill': -self.product_order.standard_price,
'billed': -2 * (self.product_a.standard_price + self.product_b.standard_price),
'to_bill': -self.product_order.standard_price * analytic_contribution,
'billed': -2 * (self.product_a.standard_price + self.product_b.standard_price) * analytic_contribution,
},
},
)
@@ -169,18 +174,18 @@ class TestProjectPurchaseProfitability(TestProjectProfitabilityCommon, TestPurch
'id': 'purchase_order',
'sequence': self.project._get_profitability_sequence_per_invoice_type()['purchase_order'],
'to_bill': 0.0,
'billed': -self.product_order.standard_price,
'billed': -self.product_order.standard_price * analytic_contribution,
}, {
'id': 'other_purchase_costs',
'sequence': self.project._get_profitability_sequence_per_invoice_type()['other_purchase_costs'],
'to_bill': 0.0,
'billed': -2 * (self.product_a.standard_price + self.product_b.standard_price),
'billed': -2 * (self.product_a.standard_price + self.product_b.standard_price) * analytic_contribution,
}],
'total': {
'to_bill': 0.0,
'billed': -(2 * self.product_a.standard_price +
2 * self.product_b.standard_price +
self.product_order.standard_price),
self.product_order.standard_price) * analytic_contribution,
},
},
)
+6 -5
View File
@@ -434,7 +434,7 @@ class Project(models.Model):
# account_move_line__move_id is the alias of the joined table account_move in the query
# we can use it, because of the "move_id.move_type" clause in the domain of the query, which generates the join
# this is faster than a search_read followed by a browse on the move_id to retrieve the move_type of each account.move.line
query_string, query_param = query.select('price_subtotal', 'parent_state', 'account_move_line.currency_id', 'account_move_line__move_id.move_type')
query_string, query_param = query.select('price_subtotal', 'parent_state', 'account_move_line.currency_id', 'account_move_line.analytic_distribution', 'account_move_line__move_id.move_type')
self._cr.execute(query_string, query_param)
invoices_move_line_read = self._cr.dictfetchall()
if invoices_move_line_read:
@@ -447,16 +447,17 @@ class Project(models.Model):
amount_invoiced = amount_to_invoice = 0.0
for moves_read in invoices_move_line_read:
price_subtotal = self.analytic_account_id.currency_id.round(moves_read['price_subtotal'] * conversion_rates[moves_read['currency_id']])
analytic_contribution = moves_read['analytic_distribution'][str(self.analytic_account_id.id)] / 100.
if moves_read['parent_state'] == 'draft':
if moves_read['move_type'] == 'out_invoice':
amount_to_invoice += price_subtotal
amount_to_invoice += price_subtotal * analytic_contribution
else: # moves_read['move_type'] == 'out_refund'
amount_to_invoice -= price_subtotal
amount_to_invoice -= price_subtotal * analytic_contribution
else: # moves_read['parent_state'] == 'posted'
if moves_read['move_type'] == 'out_invoice':
amount_invoiced += price_subtotal
amount_invoiced += price_subtotal * analytic_contribution
else: # moves_read['move_type'] == 'out_refund'
amount_invoiced -= price_subtotal
amount_invoiced -= price_subtotal * analytic_contribution
# don't display the section if the final values are both 0 (invoice -> credit note)
if amount_invoiced != 0 or amount_to_invoice != 0:
section_id = 'other_invoice_revenues'
@@ -245,8 +245,13 @@ class TestSaleProjectProfitability(TestProjectProfitabilityCommon, TestSaleCommo
"""
An invoice that has an AAL on one of its line should be taken into account
for the profitability of the project.
The contribution of the line should only be dependent
on the project's analytic account % that was set on the line
"""
self.project.allow_billable = True
# a custom analytic contribution (number between 1 -> 100 included)
analytic_distribution = 42
analytic_contribution = analytic_distribution / 100.
# create a invoice_1 with the AAL
invoice_1 = self.env['account.move'].create({
"name": "Invoice_1",
@@ -255,7 +260,7 @@ class TestSaleProjectProfitability(TestProjectProfitabilityCommon, TestSaleCommo
"partner_id": self.partner.id,
"invoice_date": datetime.today(),
"invoice_line_ids": [Command.create({
"analytic_distribution": {self.analytic_account.id: 100},
"analytic_distribution": {self.analytic_account.id: analytic_distribution},
"product_id": self.product_a.id,
"quantity": 1,
"product_uom_id": self.product_a.uom_id.id,
@@ -269,10 +274,10 @@ class TestSaleProjectProfitability(TestProjectProfitabilityCommon, TestSaleCommo
'data': [{
'id': 'other_invoice_revenues',
'sequence': self.project._get_profitability_sequence_per_invoice_type()['other_invoice_revenues'],
'to_invoice': self.product_a.standard_price,
'to_invoice': self.product_a.standard_price * analytic_contribution,
'invoiced': 0.0,
}],
'total': {'to_invoice': self.product_a.standard_price, 'invoiced': 0.0},
'total': {'to_invoice': self.product_a.standard_price * analytic_contribution, 'invoiced': 0.0},
},
)
# post invoice_1
@@ -285,9 +290,9 @@ class TestSaleProjectProfitability(TestProjectProfitabilityCommon, TestSaleCommo
'id': 'other_invoice_revenues',
'sequence': self.project._get_profitability_sequence_per_invoice_type()['other_invoice_revenues'],
'to_invoice': 0.0,
'invoiced': self.product_a.standard_price,
'invoiced': self.product_a.standard_price * analytic_contribution,
}],
'total': {'to_invoice': 0.0, 'invoiced': self.product_a.standard_price},
'total': {'to_invoice': 0.0, 'invoiced': self.product_a.standard_price * analytic_contribution},
},
)
# create another invoice, with 2 lines, 2 diff products, the second line has 2 as quantity
@@ -298,13 +303,13 @@ class TestSaleProjectProfitability(TestProjectProfitabilityCommon, TestSaleCommo
"partner_id": self.partner.id,
"invoice_date": datetime.today(),
"invoice_line_ids": [Command.create({
"analytic_distribution": {self.analytic_account.id: 100},
"analytic_distribution": {self.analytic_account.id: analytic_distribution},
"product_id": self.product_a.id,
"quantity": 1,
"product_uom_id": self.product_a.uom_id.id,
"price_unit": self.product_a.standard_price,
}), Command.create({
"analytic_distribution": {self.analytic_account.id: 100},
"analytic_distribution": {self.analytic_account.id: analytic_distribution},
"product_id": self.product_b.id,
"quantity": 2,
"product_uom_id": self.product_b.uom_id.id,
@@ -318,12 +323,12 @@ class TestSaleProjectProfitability(TestProjectProfitabilityCommon, TestSaleCommo
'data': [{
'id': 'other_invoice_revenues',
'sequence': self.project._get_profitability_sequence_per_invoice_type()['other_invoice_revenues'],
'to_invoice': (self.product_a.standard_price + 2 * self.product_b.standard_price),
'invoiced': self.product_a.standard_price,
'to_invoice': (self.product_a.standard_price + 2 * self.product_b.standard_price) * analytic_contribution,
'invoiced': self.product_a.standard_price * analytic_contribution,
}],
'total': {
'to_invoice': (self.product_a.standard_price + 2 * self.product_b.standard_price),
'invoiced': self.product_a.standard_price,
'to_invoice': (self.product_a.standard_price + 2 * self.product_b.standard_price) * analytic_contribution,
'invoiced': self.product_a.standard_price * analytic_contribution,
},
},
)
@@ -337,11 +342,11 @@ class TestSaleProjectProfitability(TestProjectProfitabilityCommon, TestSaleCommo
'id': 'other_invoice_revenues',
'sequence': self.project._get_profitability_sequence_per_invoice_type()['other_invoice_revenues'],
'to_invoice': 0.0,
'invoiced': 2 * (self.product_a.standard_price + self.product_b.standard_price),
'invoiced': 2 * (self.product_a.standard_price + self.product_b.standard_price) * analytic_contribution,
}],
'total': {
'to_invoice': 0.0,
'invoiced': 2 * (self.product_a.standard_price + self.product_b.standard_price),
'invoiced': 2 * (self.product_a.standard_price + self.product_b.standard_price) * analytic_contribution,
},
},
)