odoo18/addons/stock_landed_costs/tests/test_stockvaluationlayer.py

756 lines
30 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
""" Implementation of "INVENTORY VALUATION TESTS (With valuation layers)" spreadsheet. """
from odoo import fields
from odoo.tests import Form, tagged
from odoo.addons.stock_landed_costs.tests.common import TestStockLandedCostsCommon
from freezegun import freeze_time
import time
class TestStockValuationLCCommon(TestStockLandedCostsCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.product1 = cls.env['product.product'].create({
'name': 'product1',
'is_storable': True,
'categ_id': cls.stock_account_product_categ.id,
})
cls.productlc1 = cls.env['product.product'].create({
'name': 'product1',
'type': 'service',
'categ_id': cls.stock_account_product_categ.id,
'landed_cost_ok': True,
})
def setUp(self):
super().setUp()
self.days = 0
def _get_stock_input_move_lines(self):
return self.env['account.move.line'].search([
('account_id', '=', self.company_data['default_account_stock_in'].id),
], order='id')
def _get_stock_output_move_lines(self):
return self.env['account.move.line'].search([
('account_id', '=', self.company_data['default_account_stock_out'].id),
], order='id')
def _get_stock_valuation_move_lines(self):
return self.env['account.move.line'].search([
('account_id', '=', self.company_data['default_account_stock_valuation'].id),
], order='id')
def _get_payable_move_lines(self):
return self.env['account.move.line'].search([
('account_id', '=', self.company_data['default_account_payable'].id),
], order='id')
def _get_expense_move_lines(self):
return self.env['account.move.line'].search([
('account_id', '=', self.company_data['default_account_expense'].id),
], order='id')
def _make_lc(self, move, amount):
picking = move.picking_id
lc = Form(self.env['stock.landed.cost'])
lc.account_journal_id = self.stock_journal
lc.picking_ids.add(move.picking_id)
with lc.cost_lines.new() as cost_line:
cost_line.product_id = self.productlc1
cost_line.price_unit = amount
lc = lc.save()
lc.compute_landed_cost()
lc.button_validate()
return lc
def _make_in_move(self, product, quantity, unit_cost=None, create_picking=False, product_uom=False):
""" Helper to create and validate a receipt move.
"""
unit_cost = unit_cost or product.standard_price
in_move = self.env['stock.move'].create({
'name': 'in %s units @ %s per unit' % (str(quantity), str(unit_cost)),
'product_id': 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': product_uom.id if product_uom else self.env.ref('uom.product_uom_unit').id,
'product_uom_qty': quantity,
'price_unit': unit_cost,
'picking_type_id': self.company_data['default_warehouse'].in_type_id.id,
})
if create_picking:
picking = self.env['stock.picking'].create({
'picking_type_id': in_move.picking_type_id.id,
'location_id': in_move.location_id.id,
'location_dest_id': in_move.location_dest_id.id,
})
in_move.write({'picking_id': picking.id})
in_move._action_confirm()
in_move._action_assign()
in_move.move_line_ids.quantity = quantity
in_move.picked = True
in_move._action_done()
self.days += 1
return in_move.with_context(svl=True)
def _make_out_move(self, product, quantity, force_assign=None, create_picking=False):
""" Helper to create and validate a delivery move.
"""
out_move = self.env['stock.move'].create({
'name': 'out %s units' % str(quantity),
'product_id': product.id,
'location_id': self.company_data['default_warehouse'].lot_stock_id.id,
'location_dest_id': self.env.ref('stock.stock_location_customers').id,
'product_uom': self.env.ref('uom.product_uom_unit').id,
'product_uom_qty': quantity,
'picking_type_id': self.company_data['default_warehouse'].out_type_id.id,
})
if create_picking:
picking = self.env['stock.picking'].create({
'picking_type_id': out_move.picking_type_id.id,
'location_id': out_move.location_id.id,
'location_dest_id': out_move.location_dest_id.id,
})
out_move.write({'picking_id': picking.id})
out_move._action_confirm()
out_move._action_assign()
if force_assign:
self.env['stock.move.line'].create({
'move_id': out_move.id,
'product_id': out_move.product_id.id,
'product_uom_id': out_move.product_uom.id,
'location_id': out_move.location_id.id,
'location_dest_id': out_move.location_dest_id.id,
})
out_move.move_line_ids.quantity = quantity
out_move.picked = True
out_move._action_done()
self.days += 1
return out_move.with_context(svl=True)
@tagged('-at_install', 'post_install')
class TestStockValuationLCFIFO(TestStockValuationLCCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.product1.product_tmpl_id.categ_id.property_cost_method = 'fifo'
cls.product1.product_tmpl_id.categ_id.property_valuation = 'real_time'
def test_normal_1(self):
move1 = self._make_in_move(self.product1, 10, unit_cost=10, create_picking=True)
move2 = self._make_in_move(self.product1, 10, unit_cost=20)
lc = self._make_lc(move1, 100)
move3 = self._make_out_move(self.product1, 1)
self.assertEqual(self.product1.value_svl, 380)
self.assertEqual(self.product1.quantity_svl, 19)
self.assertEqual(self.product1.standard_price, 20)
def test_negative_1(self):
self.product1.standard_price = 10
move1 = self._make_out_move(self.product1, 2, force_assign=True)
move2 = self._make_in_move(self.product1, 10, unit_cost=15, create_picking=True)
lc = self._make_lc(move2, 100)
self.assertEqual(self.product1.value_svl, 200)
self.assertEqual(self.product1.quantity_svl, 8)
def test_alreadyout_1(self):
move1 = self._make_in_move(self.product1, 10, unit_cost=10, create_picking=True)
move2 = self._make_out_move(self.product1, 10)
lc = self._make_lc(move1, 100)
self.assertEqual(self.product1.value_svl, 0)
self.assertEqual(self.product1.quantity_svl, 0)
def test_alreadyout_2(self):
move1 = self._make_in_move(self.product1, 10, unit_cost=10, create_picking=True)
move2 = self._make_in_move(self.product1, 10, unit_cost=20)
move2 = self._make_out_move(self.product1, 1)
lc = self._make_lc(move1, 100)
self.assertEqual(self.product1.value_svl, 380)
self.assertEqual(self.product1.quantity_svl, 19)
def test_alreadyout_3(self):
move1 = self._make_in_move(self.product1, 10, unit_cost=10, create_picking=True)
move2 = self._make_out_move(self.product1, 10)
move1.move_line_ids.quantity = 15
lc = self._make_lc(move1, 60)
self.assertEqual(self.product1.value_svl, 70)
self.assertEqual(self.product1.quantity_svl, 5)
def test_fifo_to_standard_1(self):
move1 = self._make_in_move(self.product1, 10, unit_cost=10)
move2 = self._make_in_move(self.product1, 10, unit_cost=15)
move3 = self._make_out_move(self.product1, 5)
lc = self._make_lc(move1, 100)
self.product1.product_tmpl_id.categ_id.property_cost_method = 'standard'
out_svl = self.product1.stock_valuation_layer_ids.sorted()[-2]
in_svl = self.product1.stock_valuation_layer_ids.sorted()[-1]
self.assertEqual(out_svl.value, -250)
# 15 * 16.66
self.assertAlmostEqual(in_svl.value, 249.9)
def test_rounding_1(self):
"""3@100, out 1, out 1, out 1"""
move1 = self._make_in_move(self.product1, 3, unit_cost=20, create_picking=True)
lc = self._make_lc(move1, 40)
move2 = self._make_out_move(self.product1, 1)
move3 = self._make_out_move(self.product1, 1)
move4 = self._make_out_move(self.product1, 1)
self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), [60.0, 40.0, -33.33, -33.34, -33.33])
self.assertEqual(self.product1.value_svl, 0)
self.assertEqual(self.product1.quantity_svl, 0)
def test_rounding_2(self):
"""3@98, out 1, out 1, out 1"""
move1 = self._make_in_move(self.product1, 3, unit_cost=20, create_picking=True)
lc = self._make_lc(move1, 38)
move2 = self._make_out_move(self.product1, 1)
move3 = self._make_out_move(self.product1, 1)
move4 = self._make_out_move(self.product1, 1)
self.assertEqual(move2.stock_valuation_layer_ids.value, -32.67)
self.assertEqual(move3.stock_valuation_layer_ids.value, -32.67)
self.assertAlmostEqual(move4.stock_valuation_layer_ids.value, -32.66, delta=0.01) # self.env.company.currency_id.round(-32.66) -> -32.660000000000004
self.assertEqual(self.product1.value_svl, 0)
self.assertEqual(self.product1.quantity_svl, 0)
def test_rounding_3(self):
"""3@4.85, out 1, out 1, out 1"""
move1 = self._make_in_move(self.product1, 3, unit_cost=1, create_picking=True)
lc = self._make_lc(move1, 1.85)
move2 = self._make_out_move(self.product1, 1)
move3 = self._make_out_move(self.product1, 1)
move4 = self._make_out_move(self.product1, 1)
self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), [3.0, 1.85, -1.62, -1.62, -1.61])
self.assertEqual(self.product1.value_svl, 0)
self.assertEqual(self.product1.quantity_svl, 0)
def test_in_and_out_1(self):
move1 = self._make_in_move(self.product1, 10, unit_cost=100, create_picking=True)
self.assertEqual(move1.stock_valuation_layer_ids[0].remaining_value, 1000)
lc1 = self._make_lc(move1, 100)
self.assertEqual(move1.stock_valuation_layer_ids[0].remaining_value, 1100)
lc2 = self._make_lc(move1, 50)
self.assertEqual(move1.stock_valuation_layer_ids[0].remaining_value, 1150)
self.assertEqual(self.product1.value_svl, 1150)
self.assertEqual(self.product1.quantity_svl, 10)
move2 = self._make_out_move(self.product1, 1)
self.assertEqual(move2.stock_valuation_layer_ids.value, -115)
def test_landed_cost_different_uom(self):
"""
Check that the SVL is correctly updated with the landed cost divided by the quantity in the product UOM.
"""
uom_gram = self.env.ref('uom.product_uom_gram')
uom_kgm = self.env.ref('uom.product_uom_kgm')
# the product uom is in gram but the transfer is in kg
self.product1.uom_id = uom_gram
move1 = self._make_in_move(self.product1, 1, unit_cost=10, create_picking=True, product_uom=uom_kgm)
self.assertEqual(move1.stock_valuation_layer_ids[0].remaining_value, 10000)
self.assertEqual(move1.stock_valuation_layer_ids[0].remaining_qty, 1000)
self._make_lc(move1, 250)
self.assertEqual(move1.stock_valuation_layer_ids[0].remaining_value, 10250)
@tagged('-at_install', 'post_install')
class TestStockValuationLCAVCO(TestStockValuationLCCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.product1.product_tmpl_id.categ_id.property_cost_method = 'average'
cls.product1.product_tmpl_id.categ_id.property_valuation = 'real_time'
def test_normal_1(self):
move1 = self._make_in_move(self.product1, 10, unit_cost=10, create_picking=True)
move2 = self._make_in_move(self.product1, 10, unit_cost=20)
lc = self._make_lc(move1, 100)
move3 = self._make_out_move(self.product1, 1)
self.assertEqual(self.product1.value_svl, 380)
def test_negative_1(self):
self.product1.standard_price = 10
move1 = self._make_out_move(self.product1, 2, force_assign=True)
move2 = self._make_in_move(self.product1, 10, unit_cost=15, create_picking=True)
lc = self._make_lc(move2, 100)
self.assertEqual(self.product1.value_svl, 200)
self.assertEqual(self.product1.quantity_svl, 8)
def test_alreadyout_1(self):
move1 = self._make_in_move(self.product1, 10, unit_cost=10, create_picking=True)
move2 = self._make_out_move(self.product1, 10)
lc = self._make_lc(move1, 100)
self.assertEqual(len(self.product1.stock_valuation_layer_ids), 2)
self.assertEqual(self.product1.value_svl, 0)
self.assertEqual(self.product1.quantity_svl, 0)
def test_alreadyout_2(self):
move1 = self._make_in_move(self.product1, 10, unit_cost=10, create_picking=True)
move2 = self._make_in_move(self.product1, 10, unit_cost=20)
move2 = self._make_out_move(self.product1, 1)
lc = self._make_lc(move1, 100)
self.assertEqual(self.product1.value_svl, 375)
self.assertEqual(self.product1.quantity_svl, 19)
def test_lc_generated_from_bill_multi_comapnies(self):
"""
In a multi-company environment:
Confirm PO, receive products, post bill and generate LC
"""
company = self.env.company
self.env.user.company_id = self.env['res.company'].create({
'name': 'Another Company',
})
po_form = Form(self.env['purchase.order'])
po_form.company_id = company
po_form.partner_id = self.partner_a
with po_form.order_line.new() as po_line:
po_line.product_id = self.product1
po_line.product_qty = 1
po_line.price_unit = 10
po_line.taxes_id.clear()
po = po_form.save()
po.button_confirm()
receipt = po.picking_ids
receipt.move_line_ids.quantity = 1
receipt.button_validate()
bill_form = Form.from_action(self.env, po.action_create_invoice())
bill_form.invoice_date = bill_form.date
with bill_form.invoice_line_ids.new() as inv_line:
inv_line.product_id = self.productlc1
inv_line.price_unit = 5
inv_line.is_landed_costs_line = True
bill = bill_form.save()
bill.action_post()
lc_form = Form.from_action(self.env, bill.button_create_landed_costs())
lc_form.picking_ids.add(receipt)
lc = lc_form.save()
lc.button_validate()
product = self.product1.with_company(company)
self.assertEqual(product.value_svl, 15)
self.assertEqual(product.quantity_svl, 1)
self.assertEqual(product.standard_price, 15)
@tagged('-at_install', 'post_install')
class TestStockValuationLCFIFOVB(TestStockValuationLCCommon):
@classmethod
def setUpClass(cls):
super(TestStockValuationLCFIFOVB, cls).setUpClass()
cls.vendor1 = cls.env['res.partner'].create({'name': 'vendor1'})
cls.vendor1.property_account_payable_id = cls.company_data['default_account_payable']
cls.vendor2 = cls.env['res.partner'].create({'name': 'vendor2'})
cls.vendor2.property_account_payable_id = cls.company_data['default_account_payable']
cls.product1.product_tmpl_id.categ_id.property_cost_method = 'fifo'
cls.product1.product_tmpl_id.categ_id.property_valuation = 'real_time'
def test_vendor_bill_flow_anglo_saxon_1(self):
"""In anglo saxon accounting, receive 10@10 and invoice. Then invoice 1@50 as a landed costs
and create a linked landed costs record.
"""
self.env.company.anglo_saxon_accounting = True
# Create an RFQ for self.product1, 10@10
rfq = Form(self.env['purchase.order'])
rfq.partner_id = self.vendor1
with rfq.order_line.new() as po_line:
po_line.product_id = self.product1
po_line.product_qty = 10
po_line.price_unit = 10
po_line.taxes_id.clear()
rfq = rfq.save()
rfq.button_confirm()
# Process the receipt
receipt = rfq.picking_ids
receipt.button_validate()
self.assertEqual(rfq.order_line.qty_received, 10)
input_aml = self._get_stock_input_move_lines()[-1]
self.assertEqual(input_aml.debit, 0)
self.assertEqual(input_aml.credit, 100)
valuation_aml = self._get_stock_valuation_move_lines()[-1]
self.assertEqual(valuation_aml.debit, 100)
self.assertEqual(valuation_aml.credit, 0)
# Create a vendor bill for the RFQ
action = rfq.action_create_invoice()
vb = self.env['account.move'].browse(action['res_id'])
vb.invoice_date = vb.date
vb.action_post()
input_aml = self._get_stock_input_move_lines()[-1]
self.assertEqual(input_aml.debit, 100)
self.assertEqual(input_aml.credit, 0)
payable_aml = self._get_payable_move_lines()[-1]
self.assertEqual(payable_aml.debit, 0)
self.assertEqual(payable_aml.credit, 100)
# Create a vendor bill for a landed cost product, post it and validate a landed cost
# linked to this vendor bill. LC; 1@50
lcvb = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
lcvb.invoice_date = lcvb.date
lcvb.partner_id = self.vendor2
with lcvb.invoice_line_ids.new() as inv_line:
inv_line.product_id = self.productlc1
inv_line.price_unit = 50
inv_line.is_landed_costs_line = True
with lcvb.invoice_line_ids.edit(0) as inv_line:
inv_line.tax_ids.clear()
lcvb = lcvb.save()
lcvb.action_post()
input_aml = self._get_stock_input_move_lines()[-1]
self.assertEqual(input_aml.debit, 50)
self.assertEqual(input_aml.credit, 0)
payable_aml = self._get_payable_move_lines()[-1]
self.assertEqual(payable_aml.debit, 0)
self.assertEqual(payable_aml.credit, 50)
lc = Form.from_action(self.env, lcvb.button_create_landed_costs())
lc.picking_ids.add(receipt)
lc = lc.save()
lc.button_validate()
self.assertEqual(lc.cost_lines.price_unit, 50)
self.assertEqual(lc.cost_lines.product_id, self.productlc1)
input_aml = self._get_stock_input_move_lines()[-1]
self.assertEqual(input_aml.debit, 0)
self.assertEqual(input_aml.credit, 50)
valuation_aml = self._get_stock_valuation_move_lines()[-1]
self.assertEqual(valuation_aml.debit, 50)
self.assertEqual(valuation_aml.credit, 0)
# Check reconciliation of input aml of lc
lc_input_aml = lc.account_move_id.line_ids.filtered(lambda aml: aml.account_id == self.company_data['default_account_stock_in'])
self.assertTrue(len(lc_input_aml.full_reconcile_id), 1)
self.assertEqual(self.product1.quantity_svl, 10)
self.assertEqual(self.product1.value_svl, 150)
def test_vendor_bill_flow_anglo_saxon_2(self):
"""In anglo saxon accounting, receive 10@10 and invoice with the addition of 1@50 as a
landed costs and create a linked landed costs record.
"""
self.env.company.anglo_saxon_accounting = True
# Create an RFQ for self.product1, 10@10
rfq = Form(self.env['purchase.order'])
rfq.partner_id = self.vendor1
with rfq.order_line.new() as po_line:
po_line.product_id = self.product1
po_line.product_qty = 10
po_line.price_unit = 10
po_line.taxes_id.clear()
rfq = rfq.save()
rfq.button_confirm()
# Process the receipt
receipt = rfq.picking_ids
receipt.button_validate()
self.assertEqual(rfq.order_line.qty_received, 10)
input_aml = self._get_stock_input_move_lines()[-1]
self.assertEqual(input_aml.debit, 0)
self.assertEqual(input_aml.credit, 100)
valuation_aml = self._get_stock_valuation_move_lines()[-1]
self.assertEqual(valuation_aml.debit, 100)
self.assertEqual(valuation_aml.credit, 0)
# Create a vendor bill for the RFQ and add to it the landed cost
vb = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
vb.partner_id = self.vendor1
vb.invoice_date = vb.date
self.productlc1.landed_cost_ok = True
with vb.invoice_line_ids.new() as inv_line:
inv_line.product_id = self.productlc1
inv_line.price_unit = 50
inv_line.is_landed_costs_line = True
vb = vb.save()
vb.action_post()
lc = Form.from_action(self.env, vb.button_create_landed_costs())
lc.picking_ids.add(receipt)
lc = lc.save()
lc.button_validate()
# Check reconciliation of input aml of lc
lc_input_aml = lc.account_move_id.line_ids.filtered(lambda aml: aml.account_id == self.company_data['default_account_stock_in'])
self.assertTrue(len(lc_input_aml.full_reconcile_id), 1)
def test_vendor_bill_flow_continental_1(self):
"""In continental accounting, receive 10@10 and invoice. Then invoice 1@50 as a landed costs
and create a linked landed costs record.
"""
self.env.company.anglo_saxon_accounting = False
# Create an RFQ for self.product1, 10@10
rfq = Form(self.env['purchase.order'])
rfq.partner_id = self.vendor1
with rfq.order_line.new() as po_line:
po_line.product_id = self.product1
po_line.product_qty = 10
po_line.price_unit = 10
po_line.taxes_id.clear()
rfq = rfq.save()
rfq.button_confirm()
# Process the receipt
receipt = rfq.picking_ids
receipt.button_validate()
self.assertEqual(rfq.order_line.qty_received, 10)
input_aml = self._get_stock_input_move_lines()[-1]
self.assertEqual(input_aml.debit, 0)
self.assertEqual(input_aml.credit, 100)
valuation_aml = self._get_stock_valuation_move_lines()[-1]
self.assertEqual(valuation_aml.debit, 100)
self.assertEqual(valuation_aml.credit, 0)
# Create a vebdor bill for the RFQ
action = rfq.action_create_invoice()
vb = self.env['account.move'].browse(action['res_id'])
vb.invoice_date = vb.date
vb.action_post()
expense_aml = self._get_expense_move_lines()[-1]
self.assertEqual(expense_aml.debit, 100)
self.assertEqual(expense_aml.credit, 0)
payable_aml = self._get_payable_move_lines()[-1]
self.assertEqual(payable_aml.debit, 0)
self.assertEqual(payable_aml.credit, 100)
# Create a vendor bill for a landed cost product, post it and validate a landed cost
# linked to this vendor bill. LC; 1@50
lcvb = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
lcvb.partner_id = self.vendor2
lcvb.invoice_date = lcvb.date
with lcvb.invoice_line_ids.new() as inv_line:
inv_line.product_id = self.productlc1
inv_line.price_unit = 50
inv_line.is_landed_costs_line = True
with lcvb.invoice_line_ids.edit(0) as inv_line:
inv_line.tax_ids.clear()
lcvb = lcvb.save()
lcvb.action_post()
expense_aml = self._get_expense_move_lines()[-1]
self.assertEqual(expense_aml.debit, 50)
self.assertEqual(expense_aml.credit, 0)
payable_aml = self._get_payable_move_lines()[-1]
self.assertEqual(payable_aml.debit, 0)
self.assertEqual(payable_aml.credit, 50)
lc = Form.from_action(self.env, lcvb.button_create_landed_costs())
lc.picking_ids.add(receipt)
lc = lc.save()
lc.button_validate()
self.assertEqual(lc.cost_lines.price_unit, 50)
self.assertEqual(lc.cost_lines.product_id, self.productlc1)
input_aml = self._get_stock_input_move_lines()[-1]
self.assertEqual(input_aml.debit, 0)
self.assertEqual(input_aml.credit, 50)
valuation_aml = self._get_stock_valuation_move_lines()[-1]
self.assertEqual(valuation_aml.debit, 50)
self.assertEqual(valuation_aml.credit, 0)
self.assertEqual(self.product1.quantity_svl, 10)
self.assertEqual(self.product1.value_svl, 150)
def test_create_landed_cost_from_bill_multi_currencies(self):
# create a vendor bill in EUR where base currency in USD
company = self.env.user.company_id
currency_grp = self.env.ref('base.group_multi_currency')
self.env.user.write({'groups_id': [(4, currency_grp.id)]})
usd_currency = self.env.ref('base.USD')
eur_currency = self.env.ref('base.EUR')
eur_currency.active = True
company.currency_id = usd_currency
invoice_date = '2023-01-01'
accounting_date = '2024-01-31'
self.cr.execute("UPDATE res_company SET currency_id = %s WHERE id = %s", (usd_currency.id, company.id))
self.env['res.currency.rate'].search([]).unlink()
self.env['res.currency.rate'].create({
'name': invoice_date,
'rate': 1.0,
'currency_id': usd_currency.id,
'company_id': company.id,
})
self.env['res.currency.rate'].create({
'name': invoice_date,
'rate': 0.5,
'currency_id': eur_currency.id,
'company_id': company.id,
})
self.env['res.currency.rate'].create({
'name': accounting_date,
'rate': 0.25,
'currency_id': eur_currency.id,
'company_id': company.id,
})
po_form = Form(self.env['purchase.order'])
po_form.partner_id = self.vendor1
with po_form.order_line.new() as po_line:
po_line.product_id = self.product1
po_line.product_qty = 1
po_line.price_unit = 10
po = po_form.save()
po.button_confirm()
receipt = po.picking_ids
receipt.move_line_ids.quantity = 1
receipt.button_validate()
action = po.action_create_invoice()
bill = self.env['account.move'].browse(action['res_id'])
bill_form = Form(bill)
bill_form.invoice_date = invoice_date
bill_form.date = accounting_date
bill_form.currency_id = eur_currency
with bill_form.invoice_line_ids.new() as inv_line:
inv_line.product_id = self.productlc1
inv_line.price_unit = 5
inv_line.currency_id = eur_currency
bill = bill_form.save()
bill.action_post()
lc_form = Form.from_action(self.env, bill.button_create_landed_costs())
lc_form.picking_ids.add(receipt)
lc = lc_form.save()
lc.button_validate()
self.assertEqual(lc.cost_lines.price_unit, 10)
@tagged('-at_install', 'post_install')
class TestAccountInvoicingWithCOA(TestStockValuationLCCommon):
def setUp(self):
self.usd = self.env.ref('base.USD')
self.eur = self.env.ref('base.EUR')
self.env.company.currency_id = self.usd
self.env['res.currency.rate'].search([]).unlink()
def create_rate(self, inv_rate):
return self.env['res.currency.rate'].create({
'name': time.strftime('%Y-%m-%d'),
'inverse_company_rate': inv_rate,
'currency_id': self.eur.id,
'company_id': self.env.company.id,
})
def _bill(self, po, qty=None, price=None):
action = po.action_create_invoice()
bill = self.env["account.move"].browse(action["res_id"])
bill.invoice_date = fields.Date.today()
if qty is not None:
bill.invoice_line_ids.quantity = qty
if price is not None:
bill.invoice_line_ids.price_unit = price
bill.action_post()
return bill
def _return(self, picking, qty):
wizard_form = Form(self.env['stock.return.picking'].with_context(active_ids=picking.ids, active_id=picking.id, active_model='stock.picking'))
wizard = wizard_form.save()
wizard.product_return_moves.quantity = qty
return_picking = wizard._create_return()
return_picking.move_ids.quantity = qty
return_picking.button_validate()
return return_picking
def _purchase_receipt(self, product, qty, price, curr):
po_form = Form(self.env['purchase.order'])
po_form.partner_id = self.env['res.partner'].browse(self.supplier_id)
po_form.currency_id = curr
with po_form.order_line.new() as po_line:
po_line.product_id = product
po_line.product_qty = qty
po_line.price_unit = price
po = po_form.save()
po.button_confirm()
receipt = po.picking_ids
receipt.move_ids.quantity = qty
receipt.button_validate()
return po, receipt
def test_fifo_return_twice_and_bill_with_landed_cost_and_multi_currency(self):
"""This check ensure that the landed cost does not prevent '_generate_price_difference_vals' to compute
the correct 'quantity already out' when handling a Return of a Return of a Receipt.
An inccorect value of 'quantity already out' would generate COGS lines in the vendor bill.
"""
self.product1.categ_id.property_cost_method = 'fifo'
self.product1.categ_id.property_valuation = 'real_time'
self.eur.active = True
with freeze_time('2025-01-01'):
self.create_rate(1.0)
po1, _ = self._purchase_receipt(self.product1, 5, 10, self.eur)
self._bill(po1)
with freeze_time('2025-01-02'):
self.create_rate(1.5)
po2, receipt02 = self._purchase_receipt(self.product1, 10, 10, self.eur)
self._make_lc(receipt02.move_ids, 10)
receipt_return = self._return(receipt02, 10)
self._return(receipt_return, 10)
with freeze_time('2025-01-03'):
self.create_rate(2.0)
bill2 = self._bill(po2)
in_acc_id = self.company_data['default_account_stock_in'].id
tax_acc_id = self.company_data['default_account_tax_purchase'].id
payable_acc_id = self.company_data['default_account_payable'].id
self.assertRecordValues(bill2.line_ids, [
{'account_id': in_acc_id, 'balance': 200.0, 'amount_currency': 100},
{'account_id': tax_acc_id, 'balance': 30.0, 'amount_currency': 15},
{'account_id': payable_acc_id, 'balance': -230.0, 'amount_currency': -115},
])