Skip to content

Commit

Permalink
[FIX] stock_account: fix computation of anglo saxon price unit
Browse files Browse the repository at this point in the history
- Create a product with Category configured with:
  * Costing Method: First In First Out (FIFO)
  * Inventory Valuation: Automated
- Create a PO to buy 2 units at $100 (PO1) and receive the products
- Create a SO to sell 2 units (SO1) and deliver the products
  => COGS of SO1 should be $200 ($100 * 2)
- Return 1 unit from previous delivery
  => COGS of SO1 should be $100
- Create a SO to sell 1 unit (SO2) and deliver product
  => COGS of SO2 is $100
- Create a PO to buy 1 unit at $200 (PO2) and receive the product
- Re-deliver the returned unit from SO1
  => COGS of SO1 should be $300 ($100 + $200)
- Create invoice for SO1 and post it
The journal items (account.move.line) corresponding to the COGS have an incorrect
value: $200 ($100 + $100), instead of $300 ($100 + $200)

The issue comes from the method computing the anglo saxon price unit.
It does not take into account quantities from stock moves that have been returned.

This commit computes the number of units that have been returned for each stock
moves used to compute the anglo saxon price unit and to take the result into account
during the computation (of the anglo saxon price unit).

opw-2501260

closes odoo#71317

X-original-commit: 4d3147a
Signed-off-by: William Henrotin <Whenrow@users.noreply.github.com>
Signed-off-by: Anh Thao PHAM <kitan191@users.noreply.github.com>
  • Loading branch information
kitan191 committed May 27, 2021
1 parent 9fc8511 commit 83136c8
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 3 deletions.
119 changes: 119 additions & 0 deletions addons/sale_stock/tests/test_anglo_saxon_valuation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1003,3 +1003,122 @@ def test_fifo_delivered_invoice_post_delivery_4(self):
revalued_anglo_expense_amls = sale_order.picking_ids.mapped('move_lines.stock_valuation_layer_ids')[-1].stock_move_id.account_move_ids[-1].mapped('line_ids')
revalued_cogs_aml = revalued_anglo_expense_amls.filtered(lambda aml: aml.account_id == self.company_data['default_account_expense'])
self.assertEqual(revalued_cogs_aml.debit, 4, 'Price difference should have correctly reflected in expense account.')

def test_fifo_delivered_invoice_post_delivery_with_return(self):
"""Receive 2@10. SO1 2@12. Return 1 from SO1. SO2 1@12. Receive 1@20.
Re-deliver returned from SO1. Invoice after delivering everything."""
self.product.categ_id.property_cost_method = 'fifo'
self.product.invoice_policy = 'delivery'

# Receive 2@10.
in_move_1 = self.env['stock.move'].create({
'name': 'a',
'product_id': self.product.id,
'location_id': self.env.ref('stock.stock_location_suppliers').id,
'location_dest_id': self.company_data['default_warehouse'].lot_stock_id.id,
'product_uom': self.product.uom_id.id,
'product_uom_qty': 2,
'price_unit': 10,
})
in_move_1._action_confirm()
in_move_1.quantity_done = 2
in_move_1._action_done()

# Create, confirm and deliver a sale order for 2@12 (SO1)
so_1 = self._so_and_confirm_two_units()
so_1.picking_ids.move_lines.quantity_done = 2
so_1.picking_ids.button_validate()

# Return 1 from SO1
stock_return_picking_form = Form(
self.env['stock.return.picking'].with_context(
active_ids=so_1.picking_ids.ids, active_id=so_1.picking_ids.ids[0], active_model='stock.picking')
)
stock_return_picking = stock_return_picking_form.save()
stock_return_picking.product_return_moves.quantity = 1.0
stock_return_picking_action = stock_return_picking.create_returns()
return_pick = self.env['stock.picking'].browse(stock_return_picking_action['res_id'])
return_pick.action_assign()
return_pick.move_lines.quantity_done = 1
return_pick._action_done()

# Create, confirm and deliver a sale order for 1@12 (SO2)
so_2 = self.env['sale.order'].create({
'partner_id': self.partner_a.id,
'order_line': [
(0, 0, {
'name': self.product.name,
'product_id': self.product.id,
'product_uom_qty': 1.0,
'product_uom': self.product.uom_id.id,
'price_unit': 12,
'tax_id': False, # no love taxes amls
})],
})
so_2.action_confirm()
so_2.picking_ids.move_lines.quantity_done = 1
so_2.picking_ids.button_validate()

# Receive 1@20
in_move_2 = self.env['stock.move'].create({
'name': 'a',
'product_id': self.product.id,
'location_id': self.env.ref('stock.stock_location_suppliers').id,
'location_dest_id': self.company_data['default_warehouse'].lot_stock_id.id,
'product_uom': self.product.uom_id.id,
'product_uom_qty': 1,
'price_unit': 20,
})
in_move_2._action_confirm()
in_move_2.quantity_done = 1
in_move_2._action_done()

# Re-deliver returned 1 from SO1
stock_redeliver_picking_form = Form(
self.env['stock.return.picking'].with_context(
active_ids=return_pick.ids, active_id=return_pick.ids[0], active_model='stock.picking')
)
stock_redeliver_picking = stock_redeliver_picking_form.save()
stock_redeliver_picking.product_return_moves.quantity = 1.0
stock_redeliver_picking_action = stock_redeliver_picking.create_returns()
redeliver_pick = self.env['stock.picking'].browse(stock_redeliver_picking_action['res_id'])
redeliver_pick.action_assign()
redeliver_pick.move_lines.quantity_done = 1
redeliver_pick._action_done()

# Invoice the sale orders
invoice_1 = so_1._create_invoices()
invoice_1.action_post()
invoice_2 = so_2._create_invoices()
invoice_2.action_post()

# Check the resulting accounting entries
amls_1 = invoice_1.line_ids
self.assertEqual(len(amls_1), 4)
stock_out_aml_1 = amls_1.filtered(lambda aml: aml.account_id == self.company_data['default_account_stock_out'])
self.assertEqual(stock_out_aml_1.debit, 0)
self.assertEqual(stock_out_aml_1.credit, 30)
cogs_aml_1 = amls_1.filtered(lambda aml: aml.account_id == self.company_data['default_account_expense'])
self.assertEqual(cogs_aml_1.debit, 30)
self.assertEqual(cogs_aml_1.credit, 0)
receivable_aml_1 = amls_1.filtered(lambda aml: aml.account_id == self.company_data['default_account_receivable'])
self.assertEqual(receivable_aml_1.debit, 24)
self.assertEqual(receivable_aml_1.credit, 0)
income_aml_1 = amls_1.filtered(lambda aml: aml.account_id == self.company_data['default_account_revenue'])
self.assertEqual(income_aml_1.debit, 0)
self.assertEqual(income_aml_1.credit, 24)

amls_2 = invoice_2.line_ids
self.assertEqual(len(amls_2), 4)
stock_out_aml_2 = amls_2.filtered(lambda aml: aml.account_id == self.company_data['default_account_stock_out'])
self.assertEqual(stock_out_aml_2.debit, 0)
self.assertEqual(stock_out_aml_2.credit, 10)
cogs_aml_2 = amls_2.filtered(lambda aml: aml.account_id == self.company_data['default_account_expense'])
self.assertEqual(cogs_aml_2.debit, 10)
self.assertEqual(cogs_aml_2.credit, 0)
receivable_aml_2 = amls_2.filtered(lambda aml: aml.account_id == self.company_data['default_account_receivable'])
self.assertEqual(receivable_aml_2.debit, 12)
self.assertEqual(receivable_aml_2.credit, 0)
income_aml_2 = amls_2.filtered(lambda aml: aml.account_id == self.company_data['default_account_revenue'])
self.assertEqual(income_aml_2.debit, 0)
self.assertEqual(income_aml_2.credit, 12)
11 changes: 8 additions & 3 deletions addons/stock_account/models/product.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from odoo.exceptions import UserError
from odoo.tools import float_is_zero, float_repr
from odoo.exceptions import ValidationError
from collections import defaultdict


class ProductTemplate(models.Model):
Expand Down Expand Up @@ -659,17 +660,21 @@ def _compute_average_price(self, qty_invoiced, qty_to_invoice, stock_moves):
if not qty_to_invoice:
return 0.0

if not qty_to_invoice:
return 0

returned_quantities = defaultdict(float)
for move in stock_moves:
if move.origin_returned_move_id:
returned_quantities[move.origin_returned_move_id.id] += abs(sum(move.stock_valuation_layer_ids.mapped('quantity')))
candidates = stock_moves\
.sudo()\
.filtered(lambda m: not (m.origin_returned_move_id and sum(m.stock_valuation_layer_ids.mapped('quantity')) >= 0))\
.mapped('stock_valuation_layer_ids')\
.sorted()
qty_to_take_on_candidates = qty_to_invoice
tmp_value = 0 # to accumulate the value taken on the candidates
for candidate in candidates:
candidate_quantity = abs(candidate.quantity)
if candidate.stock_move_id.id in returned_quantities:
candidate_quantity -= returned_quantities[candidate.stock_move_id.id]
if float_is_zero(candidate_quantity, precision_rounding=candidate.uom_id.rounding):
continue # correction entries
if not float_is_zero(qty_invoiced, precision_rounding=candidate.uom_id.rounding):
Expand Down

0 comments on commit 83136c8

Please sign in to comment.