[FIX] stock_account/sale_stock: correctly valuate the product in below use case

Below is the case of product with automated valuation:
- You bought 8 units @ 10$.
- Then you deliver 10 units of the product.
- You assumed the missing 2 should go out at a value of 10$
  but you are not sure yet as it hasn't been bought in Odoo yet.
- Afterwards, you buy missing 2 units of the same product at 12$.

In this case the product has been undervalued when delivered without stock,
the vacuum entry is the following one (this entry already takes place):

Account                         | Debit   | Credit
===================================================
Stock Valuation                 | 0.00     | 4.00
Stock Interim (Delivered)       | 4.00     | 0.00

So, on delivering product with different price,
We should create additional journal items like:
Account                         | Debit    | Credit
===================================================
Stock Interim (Delivered)       | 0.00     | 4.00
Expenses Revaluation            | 4.00     | 0.00

closes odoo/odoo#52396

Task: 2224025
Closes: #52396
Signed-off-by: Simon Lejeune (sle) <sle@openerp.com>
This commit is contained in:
Jigar Vaghela
2020-07-03 11:24:55 +00:00
committed by Ravi Gohil
parent c997700181
commit 12b4a24b97
2 changed files with 118 additions and 0 deletions
@@ -1009,3 +1009,65 @@ class TestAngloSaxonValuation(SavepointCase):
cogs_aml = amls.filtered(lambda aml: aml.account_id == self.expense_account)
self.assertEqual(cogs_aml.debit, 56)
self.assertEqual(cogs_aml.credit, 0)
def test_fifo_delivered_invoice_post_delivery_4(self):
"""Receive 8@10. Sale order 10@12. Deliver and also invoice it without receiving the 2 missing.
Now, receive 2@12. Make sure price difference is correctly reflected in expense account."""
self.product.categ_id.property_cost_method = 'fifo'
self.product.invoice_policy = 'delivery'
self.product.standard_price = 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.stock_location.id,
'product_uom': self.product.uom_id.id,
'product_uom_qty': 8,
'price_unit': 10,
})
in_move_1._action_confirm()
in_move_1.quantity_done = 8
in_move_1._action_done()
# Create and confirm a sale order for 10@12
sale_order = self.env['sale.order'].create({
'partner_id': self.customer.id,
'order_line': [
(0, 0, {
'name': self.product.name,
'product_id': self.product.id,
'product_uom_qty': 10.0,
'product_uom': self.product.uom_id.id,
'price_unit': 12,
'tax_id': False, # no love taxes amls
})],
})
sale_order.action_confirm()
# Deliver 10
sale_order.picking_ids.move_lines.quantity_done = 10
sale_order.picking_ids.button_validate()
# Invoice the sale order.
invoice = sale_order.with_context(default_journal_id=self.journal_sale.id)._create_invoices()
invoice.post()
# Make the second receipt
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.stock_location.id,
'product_uom': self.product.uom_id.id,
'product_uom_qty': 2,
'price_unit': 12,
})
in_move_2._action_confirm()
in_move_2.quantity_done = 2
in_move_2._action_done()
# check the last anglo saxon move line
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.expense_account)
self.assertEqual(revalued_cogs_aml.debit, 4, 'Price difference should have correctly reflected in expense account.')
+56
View File
@@ -437,6 +437,62 @@ class ProductProduct(models.Model):
vacuum_svl.stock_move_id._account_entry_move(
vacuum_svl.quantity, vacuum_svl.description, vacuum_svl.id, vacuum_svl.value
)
# Create the related expense entry
self._create_fifo_vacuum_anglo_saxon_expense_entry(vacuum_svl, svl_to_vacuum)
def _create_fifo_vacuum_anglo_saxon_expense_entry(self, vacuum_svl, svl_to_vacuum):
""" When product is delivered and invoiced while you don't have units in stock anymore, there are chances of that
product getting undervalued/overvalued. So, we should nevertheless take into account the fact that the product has
already been delivered and invoiced to the customer by posting the value difference in the expense account also.
Consider the below case where product is getting undervalued:
You bought 8 units @ 10$ -> You have a stock valuation of 8 units, unit cost 10.
Then you deliver 10 units of the product.
You assumed the missing 2 should go out at a value of 10$ but you are not sure yet as it hasn't been bought in Odoo yet.
Afterwards, you buy missing 2 units of the same product at 12$ instead of expected 10$.
In case the product has been undervalued when delivered without stock, the vacuum entry is the following one (this entry already takes place):
Account | Debit | Credit
===================================================
Stock Valuation | 0.00 | 4.00
Stock Interim (Delivered) | 4.00 | 0.00
So, on delivering product with different price, We should create additional journal items like:
Account | Debit | Credit
===================================================
Stock Interim (Delivered) | 0.00 | 4.00
Expenses Revaluation | 4.00 | 0.00
"""
if not vacuum_svl.company_id.anglo_saxon_accounting or not svl_to_vacuum.stock_move_id._is_out():
return False
AccountMove = self.env['account.move'].sudo()
account_move_lines = svl_to_vacuum.account_move_id.line_ids
# Find related customer invoice where product is delivered while you don't have units in stock anymore
reconciled_line_ids = list(set(account_move_lines._reconciled_lines()) - set(account_move_lines.ids))
account_move = AccountMove.search([('line_ids','in', reconciled_line_ids)], limit=1)
# If delivered quantity is not invoiced then no need to create this entry
if not account_move:
return False
accounts = svl_to_vacuum.product_id.product_tmpl_id.get_product_accounts(fiscal_pos=account_move.fiscal_position_id)
if not accounts.get('stock_output') or not accounts.get('expense'):
return False
description = "Expenses %s" % (vacuum_svl.description)
move_lines = vacuum_svl.stock_move_id._prepare_account_move_line(
vacuum_svl.quantity, vacuum_svl.value * -1,
accounts['stock_output'].id, accounts['expense'].id,
description)
new_account_move = AccountMove.sudo().create({
'journal_id': accounts['stock_journal'].id,
'line_ids': move_lines,
'date': self._context.get('force_period_date', fields.Date.context_today(self)),
'ref': description,
'stock_move_id': vacuum_svl.stock_move_id.id,
'move_type': 'entry',
})
new_account_move.post()
to_reconcile_account_move_lines = vacuum_svl.account_move_id.line_ids.filtered(lambda l: not l.reconciled and l.account_id == accounts['stock_output'] and l.account_id.reconcile)
to_reconcile_account_move_lines += new_account_move.line_ids.filtered(lambda l: not l.reconciled and l.account_id == accounts['stock_output'] and l.account_id.reconcile)
return to_reconcile_account_move_lines.reconcile()
@api.model
def _svl_empty_stock(self, description, product_category=None, product_template=None):