mirror of
https://github.com/bringout/oca-ocb-accounting.git
synced 2026-04-24 16:22:03 +02:00
19.0 vanilla
This commit is contained in:
parent
ba20ce7443
commit
768b70e05e
2357 changed files with 1057103 additions and 712486 deletions
|
|
@ -1,5 +1,5 @@
|
|||
from . import test_account_move
|
||||
from . import test_anglo_saxon_valuation_reconciliation_common
|
||||
from . import test_lot_valuation
|
||||
from . import test_stockvaluation
|
||||
from . import test_stockvaluationlayer
|
||||
from . import test_stock_valuation_layer_revaluation
|
||||
|
|
|
|||
|
|
@ -0,0 +1,458 @@
|
|||
import re
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
|
||||
from odoo import Command, fields
|
||||
from odoo.tools.misc import clean_context
|
||||
from odoo.tests import Form
|
||||
from odoo.addons.base.tests.common import BaseCommon
|
||||
|
||||
|
||||
class TestStockValuationCommon(BaseCommon):
|
||||
# Override
|
||||
@classmethod
|
||||
def _create_company(cls, **create_values):
|
||||
company = super()._create_company(**create_values)
|
||||
cls.env["account.chart.template"]._load(
|
||||
"generic_coa", company, install_demo=False
|
||||
)
|
||||
return company
|
||||
|
||||
# HELPER
|
||||
def _create_account_move(self, move_type, product, quantity=1.0, price_unit=1.0, post=True, **kwargs):
|
||||
invoice_vals = {
|
||||
"partner_id": self.vendor.id,
|
||||
"move_type": move_type,
|
||||
"invoice_date": kwargs.get('invoice_date', fields.Date.today()),
|
||||
"invoice_line_ids": [],
|
||||
}
|
||||
if kwargs.get('reversed_entry_id'):
|
||||
invoice_vals["reversed_entry_id"] = kwargs['reversed_entry_id']
|
||||
invoice = self.env["account.move"].create(invoice_vals)
|
||||
product_uom = kwargs.get('product_uom') or product.uom_id
|
||||
self.env["account.move.line"].create({
|
||||
"move_id": invoice.id,
|
||||
"display_type": "product",
|
||||
"name": "test line",
|
||||
"price_unit": price_unit,
|
||||
"quantity": quantity,
|
||||
"product_id": product.id,
|
||||
"product_uom_id": product_uom.id,
|
||||
"tax_ids": [(5, 0, 0)],
|
||||
})
|
||||
if post:
|
||||
invoice.action_post()
|
||||
return invoice
|
||||
|
||||
def _create_invoice(self, product=None, quantity=1.0, price_unit=None, post=True, **kwargs):
|
||||
return self._create_account_move("out_invoice", product, quantity, price_unit, post, **kwargs)
|
||||
|
||||
def _create_bill(self, product=None, quantity=1.0, price_unit=None, post=True, **kwargs):
|
||||
return self._create_account_move("in_invoice", product, quantity, price_unit, post, **kwargs)
|
||||
|
||||
def _create_credit_note(self, product, quantity=1.0, price_unit=1.0, post=True, **kwargs):
|
||||
move_type = kwargs.pop("move_type", "out_refund")
|
||||
return self._create_account_move(move_type, product, quantity, price_unit, post, **kwargs)
|
||||
|
||||
def _refund(self, move_to_refund, quantity=None, post=True):
|
||||
reversal = self.env['account.move.reversal'].with_context(active_ids=move_to_refund.ids, active_model='account.move').create({
|
||||
'journal_id': move_to_refund.journal_id.id,
|
||||
})
|
||||
credit_note = self.env['account.move'].browse(reversal.refund_moves()['res_id'])
|
||||
if quantity:
|
||||
credit_note.line_ids.quantity = quantity
|
||||
if post:
|
||||
credit_note.action_post()
|
||||
return credit_note
|
||||
|
||||
def _close(self, auto_post=True, at_date=None):
|
||||
action = self.company.action_close_stock_valuation(at_date=at_date, auto_post=auto_post)
|
||||
return action['res_id'] and self.env['account.move'].browse(action['res_id'])
|
||||
|
||||
def _use_price_diff(self):
|
||||
self.account_price_diff = self.env['account.account'].create({
|
||||
'name': 'Price Difference Account',
|
||||
'code': '100102',
|
||||
'account_type': 'asset_current',
|
||||
})
|
||||
self.category_standard.property_price_difference_account_id = self.account_price_diff.id
|
||||
self.category_standard_auto.property_price_difference_account_id = self.account_price_diff.id
|
||||
return self.account_price_diff
|
||||
|
||||
def _use_route_mto(self, product):
|
||||
if not self.route_mto.active:
|
||||
self.route_mto.active = True
|
||||
product.route_ids = [(4, self.route_mto.id)]
|
||||
return product
|
||||
|
||||
def _use_multi_currencies(self, rates=None):
|
||||
date_1 = fields.Date.today()
|
||||
date_2 = date_1 + relativedelta(days=1)
|
||||
date_3 = date_2 + relativedelta(days=1)
|
||||
rates = rates or [
|
||||
(fields.Date.to_string(date_1), 1),
|
||||
(fields.Date.to_string(date_2), 2),
|
||||
(fields.Date.to_string(date_3), 3),
|
||||
]
|
||||
self.other_currency = self.setup_other_currency('EUR', rates=rates)
|
||||
|
||||
def _use_multi_warehouses(self):
|
||||
self.other_warehouse = self.env['stock.warehouse'].create({
|
||||
'name': 'Other Warehouse',
|
||||
'code': 'OWH',
|
||||
'company_id': self.company.id,
|
||||
})
|
||||
|
||||
def _use_inventory_location_accounting(self):
|
||||
self.account_inventory = self.env['account.account'].create({
|
||||
'name': 'Inventory Account',
|
||||
'code': '100101',
|
||||
'account_type': 'asset_current',
|
||||
})
|
||||
inventory_locations = self.env['stock.location'].search([('usage', '=', 'inventory'), ('company_id', '=', self.company.id)])
|
||||
inventory_locations.valuation_account_id = self.account_inventory.id
|
||||
return self.account_inventory
|
||||
|
||||
# Moves
|
||||
def _make_in_move(self,
|
||||
product,
|
||||
quantity,
|
||||
unit_cost=None,
|
||||
create_picking=False,
|
||||
company=None,
|
||||
**kwargs,
|
||||
):
|
||||
""" Helper to create and validate a receipt move.
|
||||
|
||||
:param product: Product to move
|
||||
:param quantity: Quantity to move
|
||||
:param unit_cost: Price unit
|
||||
:param create_picking: Create the picking containing the created move
|
||||
:param company: If set, the move is created in that company's context
|
||||
and warehouse defaults are resolved from that company's warehouse.
|
||||
:param **kwargs: stock.move fields that you can override
|
||||
''location_id: origin location for the move
|
||||
''location_dest_id: destination location for the move
|
||||
''lot_ids: list of lot (split among the quantity)
|
||||
''picking_type_id: picking type
|
||||
''uom_id: Unit of measure
|
||||
''owner_id: Consignment owner
|
||||
"""
|
||||
env = self.env['stock.move'].with_company(company).env if company else self.env
|
||||
if company:
|
||||
warehouse = env['stock.warehouse'].search([('company_id', '=', company.id)], limit=1)
|
||||
default_dest = warehouse.lot_stock_id.id
|
||||
default_picking_type = warehouse.in_type_id.id
|
||||
else:
|
||||
default_dest = self.stock_location.id
|
||||
default_picking_type = self.picking_type_in.id
|
||||
|
||||
product_qty = quantity
|
||||
if kwargs.get('uom_id'):
|
||||
uom = self.env['uom.uom'].browse(kwargs.get('uom_id'))
|
||||
product_qty = uom._compute_quantity(quantity, product.uom_id)
|
||||
move_vals = {
|
||||
'product_id': product.id,
|
||||
'location_id': kwargs.get('location_id', self.supplier_location.id),
|
||||
'location_dest_id': kwargs.get('location_dest_id', default_dest),
|
||||
'product_uom': kwargs.get('uom_id', self.uom.id),
|
||||
'product_uom_qty': quantity,
|
||||
'picking_type_id': kwargs.get('picking_type_id', default_picking_type),
|
||||
}
|
||||
if unit_cost:
|
||||
move_vals['value_manual'] = unit_cost * product_qty
|
||||
move_vals['price_unit'] = unit_cost
|
||||
else:
|
||||
move_vals['value_manual'] = product.standard_price * product_qty
|
||||
in_move = env['stock.move'].create(move_vals)
|
||||
|
||||
if create_picking:
|
||||
picking = 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,
|
||||
'owner_id': kwargs.get('owner_id', False),
|
||||
'partner_id': kwargs.get('partner_id', False),
|
||||
})
|
||||
in_move.picking_id = picking.id
|
||||
|
||||
in_move._action_confirm()
|
||||
lot_ids = kwargs.get('lot_ids')
|
||||
if lot_ids:
|
||||
in_move.move_line_ids.unlink()
|
||||
in_move.move_line_ids = [Command.create({
|
||||
'location_id': self.supplier_location.id,
|
||||
'location_dest_id': in_move.location_dest_id.id,
|
||||
'quantity': quantity / len(lot_ids),
|
||||
'product_id': product.id,
|
||||
'lot_id': lot.id,
|
||||
}) for lot in lot_ids]
|
||||
else:
|
||||
in_move._action_assign()
|
||||
|
||||
if not create_picking and kwargs.get('owner_id'):
|
||||
in_move.move_line_ids.owner_id = kwargs.get('owner_id')
|
||||
|
||||
in_move.picked = True
|
||||
if create_picking:
|
||||
picking.button_validate()
|
||||
else:
|
||||
in_move._action_done()
|
||||
|
||||
return in_move
|
||||
|
||||
def _make_out_move(self,
|
||||
product,
|
||||
quantity,
|
||||
force_assign=True,
|
||||
create_picking=False,
|
||||
company=None,
|
||||
**kwargs,
|
||||
):
|
||||
""" Helper to create and validate a delivery move.
|
||||
|
||||
:param product: Product to move
|
||||
:param quantity: Quantity to move
|
||||
:param force_assign: Bypass reservation to force the required quantity
|
||||
:param create_picking: Create the picking containing the created move
|
||||
:param company: If set, the move is created in that company's context
|
||||
and warehouse defaults are resolved from that company's warehouse.
|
||||
:param **kwargs: stock.move fields that you can override
|
||||
''location_id: origin location for the move
|
||||
''location_dest_id: destination location for the move
|
||||
''lot_ids: list of lot (split among the quantity)
|
||||
''picking_type_id: picking type
|
||||
''uom_id: Unit of measure
|
||||
''owner_id: Consignment owner
|
||||
"""
|
||||
env = self.env['stock.move'].with_company(company).env if company else self.env
|
||||
if company:
|
||||
warehouse = env['stock.warehouse'].search([('company_id', '=', company.id)], limit=1)
|
||||
default_src = warehouse.lot_stock_id.id
|
||||
default_picking_type = warehouse.out_type_id.id
|
||||
else:
|
||||
default_src = self.stock_location.id
|
||||
default_picking_type = self.picking_type_out.id
|
||||
|
||||
out_move = env['stock.move'].create({
|
||||
'product_id': product.id,
|
||||
'location_id': kwargs.get('location_id', default_src),
|
||||
'location_dest_id': kwargs.get('location_dest_id', self.customer_location.id),
|
||||
'product_uom': kwargs.get('uom_id', self.uom.id),
|
||||
'product_uom_qty': quantity,
|
||||
'picking_type_id': kwargs.get('picking_type_id', default_picking_type),
|
||||
})
|
||||
|
||||
if create_picking:
|
||||
picking = 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.picking_id = picking.id
|
||||
|
||||
out_move._action_confirm()
|
||||
out_move._action_assign()
|
||||
lot_ids = kwargs.get('lot_ids')
|
||||
if lot_ids:
|
||||
out_move.move_line_ids.unlink()
|
||||
out_move.move_line_ids = [Command.create({
|
||||
'location_id': out_move.location_id.id,
|
||||
'location_dest_id': self.customer_location.id,
|
||||
'quantity': quantity / len(lot_ids),
|
||||
'product_id': product.id,
|
||||
'lot_id': lot.id,
|
||||
}) for lot in lot_ids]
|
||||
elif force_assign:
|
||||
out_move.quantity = quantity
|
||||
out_move.picked = True
|
||||
out_move._action_done()
|
||||
|
||||
return out_move
|
||||
|
||||
def _make_dropship_move(self,
|
||||
product,
|
||||
quantity,
|
||||
unit_cost=None,
|
||||
create_picking=False,
|
||||
company=None,
|
||||
**kwargs,
|
||||
):
|
||||
kwargs.setdefault('location_dest_id', self.customer_location.id)
|
||||
return self._make_in_move(product, quantity, unit_cost, create_picking, company, **kwargs)
|
||||
|
||||
def _make_return(self, move, quantity_to_return):
|
||||
stock_return_picking = Form(self.env['stock.return.picking']
|
||||
.with_context(active_ids=[move.picking_id.id], active_id=move.picking_id.id, active_model='stock.picking'))
|
||||
stock_return_picking = stock_return_picking.save()
|
||||
stock_return_picking.product_return_moves.quantity = quantity_to_return
|
||||
stock_return_picking_action = stock_return_picking.action_create_returns()
|
||||
return_pick = self.env['stock.picking'].browse(stock_return_picking_action['res_id'])
|
||||
return_pick.move_ids[0].quantity = quantity_to_return
|
||||
return_pick.move_ids[0].picked = True
|
||||
return_pick._action_done()
|
||||
return return_pick.move_ids
|
||||
|
||||
# Post move processing
|
||||
def _add_move_line(self, move, **kwargs):
|
||||
old_price_unit = move._get_price_unit()
|
||||
self.env['stock.move.line'].create({
|
||||
'move_id': move.id,
|
||||
'product_id': move.product_id.id,
|
||||
'product_uom_id': move.product_uom.id,
|
||||
'location_id': move.location_id.id,
|
||||
'location_dest_id': move.location_dest_id.id,
|
||||
} | kwargs)
|
||||
move.value_manual = old_price_unit * move.quantity
|
||||
|
||||
def _set_quantity(self, move, quantity):
|
||||
"""Helper function to retroactively change the quantity of a move.
|
||||
The total value of the product will be recomputed as a result,
|
||||
regardless of the valuation method."""
|
||||
price_unit = move._get_price_unit()
|
||||
move.quantity = quantity
|
||||
move.value_manual = price_unit * quantity
|
||||
|
||||
# GETTER
|
||||
def _get_stock_valuation_move_lines(self):
|
||||
return self.env['account.move.line'].search([
|
||||
('account_id', '=', self.account_stock_valuation.id),
|
||||
], order='date, id')
|
||||
|
||||
def _get_stock_variation_move_lines(self):
|
||||
return self.env['account.move.line'].search([
|
||||
('account_id', '=', self.account_stock_variation.id),
|
||||
], order='date, id')
|
||||
|
||||
def _get_expense_move_lines(self):
|
||||
return self.env['account.move.line'].search([
|
||||
('account_id', '=', self.account_expense.id),
|
||||
], order='date, id')
|
||||
|
||||
def _url_extract_rec_id_and_model(self, url):
|
||||
# Extract model and record ID
|
||||
action_match = re.findall(r'action-([^/]+)', url)
|
||||
model_name = self.env.ref(action_match[0]).res_model
|
||||
rec_id = re.findall(r'/(\d+)$', url)[0]
|
||||
return rec_id, model_name
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
# To move to stock common later
|
||||
cls.route_mto = cls.env.ref('stock.route_warehouse0_mto')
|
||||
cls.company = cls.env['res.company'].create({'name': 'Inventory Test Company'})
|
||||
cls.env["account.chart.template"]._load(
|
||||
"generic_coa", cls.company, install_demo=False
|
||||
)
|
||||
cls.company = cls.company.with_company(cls.company.id)
|
||||
cls.env = cls.company.env
|
||||
cls.env.invalidate_all()
|
||||
# We use the admin on tour.
|
||||
cls.user_admin = cls.env.ref('base.user_admin')
|
||||
cls.user_admin.write({
|
||||
'company_id': cls.company.id,
|
||||
'company_ids': cls.company.ids,
|
||||
})
|
||||
cls.inventory_user = cls._create_new_internal_user(name='Inventory User', login='inventory_user', groups='stock.group_stock_user')
|
||||
cls.owner = cls._create_partner(name='Consignment Owner')
|
||||
cls.warehouse = cls.env['stock.warehouse'].search([('company_id', '=', cls.company.id)], limit=1)
|
||||
cls.stock_location = cls.warehouse.lot_stock_id
|
||||
cls.customer_location = cls.env.ref('stock.stock_location_customers')
|
||||
cls.supplier_location = cls.env.ref('stock.stock_location_suppliers')
|
||||
cls.inventory_location = cls.env['stock.location'].search([
|
||||
('usage', '=', 'inventory'),
|
||||
('company_id', '=', cls.company.id)
|
||||
], limit=1)
|
||||
|
||||
cls.picking_type_in = cls.warehouse.in_type_id
|
||||
cls.picking_type_out = cls.warehouse.out_type_id
|
||||
cls.uom = cls.env.ref('uom.product_uom_unit')
|
||||
cls.uom_pack_of_6 = cls.env['uom.uom'].create({
|
||||
'name': 'Pack of 6',
|
||||
'relative_uom_id': cls.uom.id,
|
||||
'relative_factor': 6.0,
|
||||
})
|
||||
|
||||
cls.vendor = cls.env['res.partner'].create({
|
||||
'name': 'Test Vendor',
|
||||
'company_id': cls.company.id,
|
||||
})
|
||||
cls.other_company = cls._create_company(name="Other Company")
|
||||
cls.branch = cls._create_company(name="Branch Company", parent_id=cls.company.id)
|
||||
|
||||
# Stock account
|
||||
cls.account_expense = cls.company.expense_account_id
|
||||
cls.account_stock_valuation = cls.company.account_stock_valuation_id
|
||||
cls.account_stock_variation = cls.account_stock_valuation.account_stock_variation_id
|
||||
cls.account_payable = cls.company.partner_id.property_account_payable_id
|
||||
cls.account_receivable = cls.company.partner_id.property_account_receivable_id
|
||||
cls.account_income = cls.company.income_account_id
|
||||
|
||||
cls.category_standard = cls.env['product.category'].create({
|
||||
'name': 'Standard',
|
||||
'property_valuation': 'periodic',
|
||||
'property_cost_method': 'standard',
|
||||
})
|
||||
cls.category_standard_auto = cls.category_standard.copy({
|
||||
'name': 'Standard Auto',
|
||||
'property_valuation': 'real_time',
|
||||
})
|
||||
cls.category_fifo = cls.env['product.category'].create({
|
||||
'name': 'Fifo',
|
||||
'property_valuation': 'periodic',
|
||||
'property_cost_method': 'fifo',
|
||||
})
|
||||
cls.category_fifo_auto = cls.category_fifo.copy({
|
||||
'name': 'Fifo Auto',
|
||||
'property_valuation': 'real_time',
|
||||
})
|
||||
cls.category_avco = cls.env['product.category'].create({
|
||||
'name': 'Avco',
|
||||
'property_valuation': 'periodic',
|
||||
'property_cost_method': 'average',
|
||||
})
|
||||
cls.category_avco_auto = cls.category_avco.copy({
|
||||
'name': 'Avco Auto',
|
||||
'property_valuation': 'real_time',
|
||||
})
|
||||
|
||||
cls.product_common_vals = {
|
||||
"standard_price": 10.0,
|
||||
"list_price": 20.0,
|
||||
"uom_id": cls.uom.id,
|
||||
"is_storable": True,
|
||||
}
|
||||
cls.product = cls.env['product.product'].create(
|
||||
{**cls.product_common_vals, 'name': 'Storable Product'}).with_context(clean_context(cls.env.context))
|
||||
cls.product_standard = cls.env['product.product'].create({
|
||||
**cls.product_common_vals,
|
||||
'name': 'Standard Product',
|
||||
'categ_id': cls.category_standard.id,
|
||||
}).with_context(clean_context(cls.env.context))
|
||||
cls.product_standard_auto = cls.env['product.product'].create({
|
||||
**cls.product_common_vals,
|
||||
'name': 'Standard Product Auto',
|
||||
'categ_id': cls.category_standard_auto.id,
|
||||
}).with_context(clean_context(cls.env.context))
|
||||
cls.product_fifo = cls.env['product.product'].create({
|
||||
**cls.product_common_vals,
|
||||
'name': 'Fifo Product',
|
||||
'categ_id': cls.category_fifo.id,
|
||||
}).with_context(clean_context(cls.env.context))
|
||||
cls.product_fifo_auto = cls.env['product.product'].create({
|
||||
**cls.product_common_vals,
|
||||
'name': 'Fifo Product Auto',
|
||||
'categ_id': cls.category_fifo_auto.id,
|
||||
}).with_context(clean_context(cls.env.context))
|
||||
cls.product_avco = cls.env['product.product'].create({
|
||||
**cls.product_common_vals,
|
||||
'name': 'Avco Product',
|
||||
'categ_id': cls.category_avco.id,
|
||||
}).with_context(clean_context(cls.env.context))
|
||||
cls.product_avco_auto = cls.env['product.product'].create({
|
||||
**cls.product_common_vals,
|
||||
'name': 'Avco Product Auto',
|
||||
'categ_id': cls.category_avco_auto.id,
|
||||
}).with_context(clean_context(cls.env.context))
|
||||
|
|
@ -1,122 +1,85 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from freezegun import freeze_time
|
||||
|
||||
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
|
||||
from odoo.addons.stock_account.tests.test_stockvaluation import _create_accounting_data
|
||||
from odoo.tests.common import tagged, Form
|
||||
from odoo.addons.stock_account.tests.common import TestStockValuationCommon
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests import Form, tagged
|
||||
from odoo import fields, Command
|
||||
|
||||
class TestAccountMoveStockCommon(AccountTestInvoicingCommon):
|
||||
@classmethod
|
||||
def setUpClass(cls, chart_template_ref=None):
|
||||
super().setUpClass(chart_template_ref=chart_template_ref)
|
||||
|
||||
(
|
||||
cls.stock_input_account,
|
||||
cls.stock_output_account,
|
||||
cls.stock_valuation_account,
|
||||
cls.expense_account,
|
||||
cls.stock_journal,
|
||||
) = _create_accounting_data(cls.env)
|
||||
|
||||
# `all_categ` should not be altered, so we can test the `post_init` hook of `stock_account`
|
||||
cls.all_categ = cls.env.ref('product.product_category_all')
|
||||
|
||||
cls.auto_categ = cls.env['product.category'].create({
|
||||
'name': 'child_category',
|
||||
'parent_id': cls.all_categ.id,
|
||||
"property_stock_account_input_categ_id": cls.stock_input_account.id,
|
||||
"property_stock_account_output_categ_id": cls.stock_output_account.id,
|
||||
"property_stock_valuation_account_id": cls.stock_valuation_account.id,
|
||||
"property_stock_journal": cls.stock_journal.id,
|
||||
"property_valuation": "real_time",
|
||||
"property_cost_method": "standard",
|
||||
})
|
||||
cls.product_A = cls.env["product.product"].create(
|
||||
{
|
||||
"name": "Product A",
|
||||
"type": "product",
|
||||
"default_code": "prda",
|
||||
"categ_id": cls.auto_categ.id,
|
||||
"taxes_id": [(5, 0, 0)],
|
||||
"supplier_taxes_id": [(5, 0, 0)],
|
||||
"lst_price": 100.0,
|
||||
"standard_price": 10.0,
|
||||
"property_account_income_id": cls.company_data["default_account_revenue"].id,
|
||||
"property_account_expense_id": cls.company_data["default_account_expense"].id,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@tagged("post_install", "-at_install")
|
||||
class TestAccountMove(TestAccountMoveStockCommon):
|
||||
class TestAccountMove(TestStockValuationCommon):
|
||||
def test_standard_perpetual_01_mc_01(self):
|
||||
rate = self.currency_data["rates"].sorted()[0].rate
|
||||
product = self.product_standard_auto
|
||||
self._use_multi_currencies([('2017-01-01', 2.0)])
|
||||
rate = self.other_currency.rate_ids.rate
|
||||
|
||||
move_form = Form(self.env["account.move"].with_context(default_move_type="out_invoice"))
|
||||
move_form.partner_id = self.partner_a
|
||||
move_form.currency_id = self.currency_data["currency"]
|
||||
move_form.partner_id = self.partner
|
||||
move_form.currency_id = self.other_currency
|
||||
with move_form.invoice_line_ids.new() as line_form:
|
||||
line_form.product_id = self.product_A
|
||||
line_form.product_id = product
|
||||
line_form.tax_ids.clear()
|
||||
invoice = move_form.save()
|
||||
|
||||
self.assertAlmostEqual(self.product_A.lst_price * rate, invoice.amount_total)
|
||||
self.assertAlmostEqual(product.lst_price * rate, invoice.amount_total)
|
||||
self.assertEqual(len(invoice.mapped("line_ids")), 2)
|
||||
self.assertEqual(len(invoice.mapped("line_ids.currency_id")), 1)
|
||||
|
||||
invoice._post()
|
||||
|
||||
self.assertAlmostEqual(self.product_A.lst_price * rate, invoice.amount_total)
|
||||
self.assertAlmostEqual(self.product_A.lst_price * rate, invoice.amount_residual)
|
||||
self.assertAlmostEqual(product.lst_price * rate, invoice.amount_total)
|
||||
self.assertAlmostEqual(product.lst_price * rate, invoice.amount_residual)
|
||||
self.assertEqual(len(invoice.mapped("line_ids")), 4)
|
||||
self.assertEqual(len(invoice.mapped("line_ids").filtered(lambda l: l.display_type == 'cogs')), 2)
|
||||
self.assertEqual(len(invoice.mapped("line_ids.currency_id")), 2)
|
||||
|
||||
def test_fifo_perpetual_01_mc_01(self):
|
||||
self.product_A.categ_id.property_cost_method = "fifo"
|
||||
rate = self.currency_data["rates"].sorted()[0].rate
|
||||
product = self.product_fifo_auto
|
||||
self._use_multi_currencies([('2017-01-01', 2.0)])
|
||||
rate = self.other_currency.rate_ids.rate
|
||||
|
||||
move_form = Form(self.env["account.move"].with_context(default_move_type="out_invoice"))
|
||||
move_form.partner_id = self.partner_a
|
||||
move_form.currency_id = self.currency_data["currency"]
|
||||
move_form.partner_id = self.partner
|
||||
move_form.currency_id = self.other_currency
|
||||
with move_form.invoice_line_ids.new() as line_form:
|
||||
line_form.product_id = self.product_A
|
||||
line_form.product_id = product
|
||||
line_form.tax_ids.clear()
|
||||
invoice = move_form.save()
|
||||
|
||||
self.assertAlmostEqual(self.product_A.lst_price * rate, invoice.amount_total)
|
||||
self.assertAlmostEqual(product.lst_price * rate, invoice.amount_total)
|
||||
self.assertEqual(len(invoice.mapped("line_ids")), 2)
|
||||
self.assertEqual(len(invoice.mapped("line_ids.currency_id")), 1)
|
||||
|
||||
invoice._post()
|
||||
|
||||
self.assertAlmostEqual(self.product_A.lst_price * rate, invoice.amount_total)
|
||||
self.assertAlmostEqual(self.product_A.lst_price * rate, invoice.amount_residual)
|
||||
self.assertAlmostEqual(product.lst_price * rate, invoice.amount_total)
|
||||
self.assertAlmostEqual(product.lst_price * rate, invoice.amount_residual)
|
||||
self.assertEqual(len(invoice.mapped("line_ids")), 4)
|
||||
self.assertEqual(len(invoice.mapped("line_ids").filtered(lambda l: l.display_type == 'cogs')), 2)
|
||||
self.assertEqual(len(invoice.mapped("line_ids.currency_id")), 2)
|
||||
|
||||
def test_average_perpetual_01_mc_01(self):
|
||||
self.product_A.categ_id.property_cost_method = "average"
|
||||
rate = self.currency_data["rates"].sorted()[0].rate
|
||||
product = self.product_avco_auto
|
||||
self._use_multi_currencies([('2017-01-01', 2.0)])
|
||||
rate = self.other_currency.rate_ids.rate
|
||||
|
||||
move_form = Form(self.env["account.move"].with_context(default_move_type="out_invoice"))
|
||||
move_form.partner_id = self.partner_a
|
||||
move_form.currency_id = self.currency_data["currency"]
|
||||
move_form.partner_id = self.partner
|
||||
move_form.currency_id = self.other_currency
|
||||
with move_form.invoice_line_ids.new() as line_form:
|
||||
line_form.product_id = self.product_A
|
||||
line_form.product_id = product
|
||||
line_form.tax_ids.clear()
|
||||
invoice = move_form.save()
|
||||
|
||||
self.assertAlmostEqual(self.product_A.lst_price * rate, invoice.amount_total)
|
||||
self.assertAlmostEqual(product.lst_price * rate, invoice.amount_total)
|
||||
self.assertEqual(len(invoice.mapped("line_ids")), 2)
|
||||
self.assertEqual(len(invoice.mapped("line_ids.currency_id")), 1)
|
||||
|
||||
invoice._post()
|
||||
|
||||
self.assertAlmostEqual(self.product_A.lst_price * rate, invoice.amount_total)
|
||||
self.assertAlmostEqual(self.product_A.lst_price * rate, invoice.amount_residual)
|
||||
self.assertAlmostEqual(product.lst_price * rate, invoice.amount_total)
|
||||
self.assertAlmostEqual(product.lst_price * rate, invoice.amount_residual)
|
||||
self.assertEqual(len(invoice.mapped("line_ids")), 4)
|
||||
self.assertEqual(len(invoice.mapped("line_ids").filtered(lambda l: l.display_type == 'cogs')), 2)
|
||||
self.assertEqual(len(invoice.mapped("line_ids.currency_id")), 2)
|
||||
|
|
@ -125,34 +88,42 @@ class TestAccountMove(TestAccountMoveStockCommon):
|
|||
"""Storno accounting uses negative numbers on debit/credit to cancel other moves.
|
||||
This test checks that we do the same for the anglosaxon lines when storno is enabled.
|
||||
"""
|
||||
self._use_multi_currencies([('2017-01-01', 2.0)])
|
||||
|
||||
product = self.product_standard_auto
|
||||
self.env.company.account_storno = True
|
||||
self.env.company.anglo_saxon_accounting = True
|
||||
|
||||
move = self.env['account.move'].create({
|
||||
'move_type': 'out_refund',
|
||||
'invoice_date': fields.Date.from_string('2019-01-01'),
|
||||
'partner_id': self.partner_a.id,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'partner_id': self.partner.id,
|
||||
'currency_id': self.other_currency.id,
|
||||
'invoice_line_ids': [
|
||||
(0, None, {'product_id': self.product_A.id}),
|
||||
(0, None, {'product_id': product.id}),
|
||||
]
|
||||
})
|
||||
move.action_post()
|
||||
|
||||
stock_output_line = move.line_ids.filtered(lambda l: l.account_id == self.stock_output_account)
|
||||
stock_output_line = move.line_ids.filtered(lambda l: l.account_id == self.account_stock_valuation)
|
||||
self.assertEqual(stock_output_line.debit, 0)
|
||||
self.assertEqual(stock_output_line.credit, -10)
|
||||
|
||||
expense_line = move.line_ids.filtered(lambda l: l.account_id == self.product_A.property_account_expense_id)
|
||||
expense_line = move.line_ids.filtered(lambda l: l.account_id == product._get_product_accounts()['expense'])
|
||||
self.assertEqual(expense_line.debit, -10)
|
||||
self.assertEqual(expense_line.credit, 0)
|
||||
|
||||
def test_standard_manual_tax_edit(self):
|
||||
''' Test manually editing tax amount, cogs creation should not reset tax amount '''
|
||||
product = self.product_standard_auto
|
||||
product.lst_price = 100
|
||||
move_form = Form(self.env["account.move"].with_context(default_move_type="out_invoice"))
|
||||
move_form.partner_id = self.partner_a
|
||||
move_form.partner_id = self.partner
|
||||
self.company.income_account_id.write({
|
||||
'tax_ids': [(6, 0, [self.env.company.account_sale_tax_id.id])]
|
||||
})
|
||||
with move_form.invoice_line_ids.new() as line_form:
|
||||
line_form.product_id = self.product_A
|
||||
line_form.product_id = product
|
||||
invoice = move_form.save()
|
||||
|
||||
self.assertEqual(invoice.amount_total, 115)
|
||||
|
|
@ -160,36 +131,14 @@ class TestAccountMove(TestAccountMoveStockCommon):
|
|||
self.assertEqual(invoice.amount_tax, 15)
|
||||
|
||||
# simulate manual tax edit via widget
|
||||
vals = {
|
||||
'tax_totals': {
|
||||
'amount_untaxed': 100,
|
||||
'amount_total': 114,
|
||||
'formatted_amount_total': '$\xa0114.00',
|
||||
'formatted_amount_untaxed': '$\xa0100.00',
|
||||
'groups_by_subtotal': {
|
||||
'Untaxed Amount': [{
|
||||
'group_key': 2,
|
||||
'tax_group_id': 2,
|
||||
'tax_group_name': 'Tax 15%',
|
||||
'tax_group_amount': 14,
|
||||
'tax_group_base_amount': 100,
|
||||
'formatted_tax_group_amount': '$\xa014.00',
|
||||
'formatted_tax_group_base_amount': '$\xa0100.00'
|
||||
}]
|
||||
},
|
||||
'subtotals': [{
|
||||
'name': 'Untaxed Amount',
|
||||
'amount': 100,
|
||||
'formatted_amount': '$\xa0100.00'
|
||||
}],
|
||||
'subtotals_order': ['Untaxed Amount'],
|
||||
'display_tax_base': False,
|
||||
}
|
||||
}
|
||||
invoice.write(vals)
|
||||
tax_totals = invoice.tax_totals
|
||||
tax_totals['subtotals'][0]['tax_groups'][0]['tax_amount_currency'] = 14.0
|
||||
invoice.tax_totals = tax_totals
|
||||
|
||||
self.assertEqual(len(invoice.mapped("line_ids")), 3)
|
||||
self.assertAlmostEqual(114.0, invoice.amount_total)
|
||||
self.assertEqual(invoice.amount_total, 114)
|
||||
self.assertEqual(invoice.amount_untaxed, 100)
|
||||
self.assertEqual(invoice.amount_tax, 14)
|
||||
|
||||
invoice._post()
|
||||
|
||||
|
|
@ -207,108 +156,40 @@ class TestAccountMove(TestAccountMoveStockCommon):
|
|||
the post-install hook)
|
||||
- One created after the module installation
|
||||
"""
|
||||
first_company = self.env['res.company'].browse(1)
|
||||
self.env.user.company_ids |= first_company
|
||||
basic_product = self.env['product.product'].create({
|
||||
'name': 'SuperProduct',
|
||||
'type': 'product',
|
||||
'categ_id': self.all_categ.id,
|
||||
})
|
||||
self.env.user.company_ids |= self.other_company
|
||||
|
||||
for company in (self.env.company | first_company):
|
||||
for company in (self.company | self.other_company):
|
||||
bill_form = Form(self.env['account.move'].with_company(company.id).with_context(default_move_type='in_invoice'))
|
||||
bill_form.partner_id = self.partner_a
|
||||
bill_form.partner_id = self.partner
|
||||
bill_form.invoice_date = fields.Date.today()
|
||||
with bill_form.invoice_line_ids.new() as line:
|
||||
line.product_id = basic_product
|
||||
line.product_id = self.product_standard
|
||||
line.price_unit = 100
|
||||
bill = bill_form.save()
|
||||
bill.action_post()
|
||||
|
||||
product_accounts = basic_product.product_tmpl_id.with_company(company.id).get_product_accounts()
|
||||
product_accounts = self.product_standard.product_tmpl_id.with_company(company.id).get_product_accounts()
|
||||
self.assertEqual(bill.invoice_line_ids.account_id, product_accounts['expense'])
|
||||
|
||||
def test_product_valuation_method_change_to_automated_negative_on_hand_qty(self):
|
||||
"""
|
||||
We have a product whose category has manual valuation and on-hand quantity is negative:
|
||||
Upon switching to an automated valuation method for the product category, the following
|
||||
entries should be generated in the stock journal:
|
||||
1. CREDIT to valuation account
|
||||
2. DEBIT to stock output account
|
||||
"""
|
||||
stock_location = self.env['stock.warehouse'].search([
|
||||
('company_id', '=', self.env.company.id),
|
||||
], limit=1).lot_stock_id
|
||||
categ = self.env['product.category'].create({'name': 'categ'})
|
||||
product = self.product_a
|
||||
product.write({
|
||||
'type': 'product',
|
||||
'categ_id': categ.id,
|
||||
})
|
||||
|
||||
out_picking = self.env['stock.picking'].create({
|
||||
'location_id': stock_location.id,
|
||||
'location_dest_id': self.ref('stock.stock_location_customers'),
|
||||
'picking_type_id': stock_location.warehouse_id.out_type_id.id,
|
||||
})
|
||||
sm = self.env['stock.move'].create({
|
||||
'name': product.name,
|
||||
'product_id': product.id,
|
||||
'product_uom_qty': 1,
|
||||
'product_uom': product.uom_id.id,
|
||||
'location_id': out_picking.location_id.id,
|
||||
'location_dest_id': out_picking.location_dest_id.id,
|
||||
'picking_id': out_picking.id,
|
||||
})
|
||||
out_picking.action_confirm()
|
||||
sm.quantity_done = 1
|
||||
out_picking.button_validate()
|
||||
|
||||
categ.write({
|
||||
'property_valuation': 'real_time',
|
||||
'property_stock_account_input_categ_id': self.stock_input_account.id,
|
||||
'property_stock_account_output_categ_id': self.stock_output_account.id,
|
||||
'property_stock_valuation_account_id': self.stock_valuation_account.id,
|
||||
'property_stock_journal': self.stock_journal.id,
|
||||
})
|
||||
|
||||
amls = self.env['account.move.line'].search([('product_id', '=', product.id)])
|
||||
if amls[0].account_id == self.stock_valuation_account:
|
||||
stock_valuation_line = amls[0]
|
||||
output_line = amls[1]
|
||||
else:
|
||||
output_line = amls[0]
|
||||
stock_valuation_line = amls[1]
|
||||
|
||||
expected_valuation_line = {
|
||||
'account_id': self.stock_valuation_account.id,
|
||||
'credit': product.standard_price,
|
||||
'debit': 0,
|
||||
}
|
||||
expected_output_line = {
|
||||
'account_id': self.stock_output_account.id,
|
||||
'credit': 0,
|
||||
'debit': product.standard_price,
|
||||
}
|
||||
self.assertRecordValues(
|
||||
[stock_valuation_line, output_line],
|
||||
[expected_valuation_line, expected_output_line]
|
||||
)
|
||||
|
||||
def test_cogs_analytic_accounting(self):
|
||||
"""Check analytic distribution is correctly propagated to COGS lines"""
|
||||
self.env.company.anglo_saxon_accounting = True
|
||||
default_plan = self.env['account.analytic.plan'].create({'name': 'Default', 'company_id': False})
|
||||
analytic_account = self.env['account.analytic.account'].create({'name': 'Account 1', 'plan_id': default_plan.id})
|
||||
product = self.product_standard_auto
|
||||
default_plan = self.env['account.analytic.plan'].create({
|
||||
'name': 'Default',
|
||||
})
|
||||
analytic_account = self.env['account.analytic.account'].create({
|
||||
'name': 'Account 1',
|
||||
'plan_id': default_plan.id,
|
||||
'company_id': False,
|
||||
})
|
||||
|
||||
move = self.env['account.move'].create({
|
||||
'move_type': 'out_refund',
|
||||
'invoice_date': fields.Date.from_string('2019-01-01'),
|
||||
'partner_id': self.partner_a.id,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'partner_id': self.partner.id,
|
||||
'invoice_line_ids': [
|
||||
Command.create({
|
||||
'product_id': self.product_A.id,
|
||||
'product_id': product.id,
|
||||
'analytic_distribution': {
|
||||
analytic_account.id: 100,
|
||||
},
|
||||
|
|
@ -317,5 +198,144 @@ class TestAccountMove(TestAccountMoveStockCommon):
|
|||
})
|
||||
move.action_post()
|
||||
|
||||
cogs_line = move.line_ids.filtered(lambda l: l.account_id == self.product_A.property_account_expense_id)
|
||||
cogs_line = move.line_ids.filtered(lambda l: l.account_id == product._get_product_accounts()['expense'])
|
||||
self.assertEqual(cogs_line.analytic_distribution, {str(analytic_account.id): 100})
|
||||
|
||||
def test_cogs_account_branch_company(self):
|
||||
"""Check branch company accounts are selected"""
|
||||
product = self.product_standard_auto
|
||||
branch = self.branch
|
||||
test_account = self.env['account.account'].with_company(branch.id).create({
|
||||
'name': 'STCK Test Account',
|
||||
'code': '100119',
|
||||
'reconcile': True,
|
||||
'account_type': 'asset_current',
|
||||
})
|
||||
self.category_standard_auto.with_company(branch.id).property_valuation = "real_time"
|
||||
self.category_standard_auto.with_company(branch.id).property_stock_valuation_account_id = test_account
|
||||
|
||||
bill = self.env['account.move'].with_company(branch.id).with_context(default_move_type='in_invoice').create({
|
||||
'partner_id': self.partner.id,
|
||||
'invoice_date': fields.Date.today(),
|
||||
'company_id': branch.id,
|
||||
'invoice_line_ids': [
|
||||
Command.create({
|
||||
'product_id': product.id,
|
||||
'price_unit': 100,
|
||||
}),
|
||||
],
|
||||
})
|
||||
self.assertEqual(bill.invoice_line_ids.account_id, test_account)
|
||||
|
||||
def test_apply_inventory_adjustment_on_multiple_quants_simultaneously(self):
|
||||
product = self.product_standard_auto
|
||||
product_b = product.copy()
|
||||
products = product + product_b
|
||||
|
||||
self._use_inventory_location_accounting()
|
||||
|
||||
self.env['stock.quant']._update_available_quantity(product, self.stock_location, 5)
|
||||
self.env['stock.quant']._update_available_quantity(product_b, self.stock_location, 15)
|
||||
|
||||
quants = products.stock_quant_ids
|
||||
quants.inventory_quantity = 10.0
|
||||
wizard = self.env['stock.inventory.adjustment.name'].create({'quant_ids': quants})
|
||||
wizard.action_apply()
|
||||
inv_adjustment_journal_items = self.env['account.move.line'].search([('product_id', 'in', products.ids)], order='id asc', limit=4)
|
||||
prod_a_accounts = product.product_tmpl_id.get_product_accounts()
|
||||
prod_b_accounts = product_b.product_tmpl_id.get_product_accounts()
|
||||
self.assertRecordValues(
|
||||
inv_adjustment_journal_items,
|
||||
[
|
||||
{'account_id': self.account_inventory.id, 'product_id': product.id},
|
||||
{'account_id': prod_a_accounts['stock_valuation'].id, 'product_id': product.id},
|
||||
{'account_id': prod_b_accounts['stock_valuation'].id, 'product_id': product_b.id},
|
||||
{'account_id': self.account_inventory.id, 'product_id': product_b.id},
|
||||
]
|
||||
)
|
||||
|
||||
@freeze_time("2020-01-22")
|
||||
def test_backdate_picking_with_lock_date(self):
|
||||
"""
|
||||
Check that pickings can not be backdate or validated prior to the
|
||||
fiscal and hard lock date.
|
||||
"""
|
||||
self.env['account.lock_exception'].search([]).sudo().unlink()
|
||||
lock_date = fields.Date.from_string('2011-01-01')
|
||||
prior_to_lock_date = fields.Datetime.add(lock_date, days=-1)
|
||||
post_to_lock_date = fields.Datetime.add(lock_date, days=+1)
|
||||
self.env['stock.quant']._update_available_quantity(self.product_standard, self.stock_location, 10)
|
||||
receipts = receipt, receipt_done = self.env['stock.picking'].create([
|
||||
{
|
||||
'location_id': self.supplier_location.id,
|
||||
'location_dest_id': self.stock_location.id,
|
||||
'picking_type_id': self.picking_type_in.id,
|
||||
'owner_id': self.env.company.partner_id.id,
|
||||
'move_ids': [Command.create({
|
||||
'product_id': self.product_standard.id,
|
||||
'location_id': self.supplier_location.id,
|
||||
'location_dest_id': self.stock_location.id,
|
||||
'product_uom_qty': 1.0,
|
||||
})]
|
||||
} for _ in range(2)
|
||||
])
|
||||
receipts.action_confirm()
|
||||
receipt_done.button_validate()
|
||||
# Check that the purchase, sale and tax lock dates do not impose any restrictions
|
||||
self.env.company.write({
|
||||
'sale_lock_date': lock_date,
|
||||
'purchase_lock_date': lock_date,
|
||||
'tax_lock_date': lock_date,
|
||||
})
|
||||
# Receipts can be backdated
|
||||
receipt.scheduled_date = prior_to_lock_date
|
||||
receipt_done.date_done = prior_to_lock_date
|
||||
|
||||
# Check that the fiscal year lock date imposes restrictions
|
||||
self.env.company.write({
|
||||
'sale_lock_date': False,
|
||||
'purchase_lock_date': False,
|
||||
'tax_lock_date': False,
|
||||
'fiscalyear_lock_date': lock_date,
|
||||
})
|
||||
# Receipts can not be backdated prior to lock date
|
||||
receipt.scheduled_date = post_to_lock_date
|
||||
receipt_done.date_done = post_to_lock_date
|
||||
# Lock dates should not affect un-validated scheduled_date
|
||||
receipt.scheduled_date = prior_to_lock_date
|
||||
with self.assertRaises(UserError):
|
||||
receipt_done.date_done = prior_to_lock_date
|
||||
|
||||
# Check that the hard lock date imposes restrictions
|
||||
self.env.company.write({
|
||||
'fiscalyear_lock_date': False,
|
||||
'hard_lock_date': lock_date,
|
||||
})
|
||||
# Receipts can not be backdated prior to lock date
|
||||
receipt.scheduled_date = post_to_lock_date
|
||||
receipt_done.date_done = post_to_lock_date
|
||||
# Lock dates should not affect un-validated scheduled_date
|
||||
receipt.scheduled_date = prior_to_lock_date
|
||||
with self.assertRaises(UserError):
|
||||
receipt_done.date_done = prior_to_lock_date
|
||||
|
||||
def test_invoice_with_journal_item_without_label(self):
|
||||
"""Test posting an invoice whose invoice lines have no label.
|
||||
The 'name' field is optional on account.move.line and should be
|
||||
handled safely when generating accounting entries.
|
||||
"""
|
||||
move = self.env['account.move'].create({
|
||||
'move_type': 'out_invoice',
|
||||
'partner_id': self.partner.id,
|
||||
'invoice_line_ids': [
|
||||
Command.create({
|
||||
'product_id': self.product_standard.id,
|
||||
'name': False,
|
||||
}),
|
||||
],
|
||||
})
|
||||
move.action_post()
|
||||
# name should remain falsy on the invoice line
|
||||
self.assertFalse(move.invoice_line_ids.name)
|
||||
# ensure the invoice is posted successfully
|
||||
self.assertEqual(move.state, 'posted')
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from freezegun import freeze_time
|
||||
|
||||
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
|
||||
from odoo.tests import tagged
|
||||
from odoo import fields
|
||||
|
||||
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
|
||||
|
||||
|
||||
class ValuationReconciliationTestCommon(AccountTestInvoicingCommon):
|
||||
""" Base class for tests checking interim accounts reconciliation works
|
||||
|
|
@ -15,109 +14,67 @@ class ValuationReconciliationTestCommon(AccountTestInvoicingCommon):
|
|||
"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls, chart_template_ref=None):
|
||||
super().setUpClass(chart_template_ref=chart_template_ref)
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.stock_account_product_categ = cls.env['product.category'].create({
|
||||
'name': 'Test category',
|
||||
'property_valuation': 'real_time',
|
||||
'property_cost_method': 'fifo',
|
||||
'property_stock_valuation_account_id': cls.company_data['default_account_stock_valuation'].id,
|
||||
'property_stock_account_input_categ_id': cls.company_data['default_account_stock_in'].id,
|
||||
'property_stock_account_output_categ_id': cls.company_data['default_account_stock_out'].id,
|
||||
})
|
||||
|
||||
uom_unit = cls.env.ref('uom.product_uom_unit')
|
||||
|
||||
cls.test_product_order = cls.env['product.product'].create({
|
||||
'name': "Test product template invoiced on order",
|
||||
'standard_price': 42.0,
|
||||
'type': 'product',
|
||||
'is_storable': True,
|
||||
'categ_id': cls.stock_account_product_categ.id,
|
||||
'uom_id': uom_unit.id,
|
||||
'uom_po_id': uom_unit.id,
|
||||
'uom_id': cls.uom_unit.id,
|
||||
})
|
||||
cls.test_product_delivery = cls.env['product.product'].create({
|
||||
'name': 'Test product template invoiced on delivery',
|
||||
'standard_price': 42.0,
|
||||
'type': 'product',
|
||||
'is_storable': True,
|
||||
'categ_id': cls.stock_account_product_categ.id,
|
||||
'uom_id': uom_unit.id,
|
||||
'uom_po_id': uom_unit.id,
|
||||
'uom_id': cls.uom_unit.id,
|
||||
})
|
||||
|
||||
cls.res_users_stock_user = cls.env['res.users'].create({
|
||||
'name': "Inventory User",
|
||||
'login': "su",
|
||||
'email': "stockuser@yourcompany.com",
|
||||
'groups_id': [(6, 0, [cls.env.ref('stock.group_stock_user').id])],
|
||||
})
|
||||
'group_ids': [(6, 0, [cls.env.ref('stock.group_stock_user').id])],
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def setup_company_data(cls, company_name, chart_template=None, **kwargs):
|
||||
company_data = super().setup_company_data(company_name, chart_template=chart_template, **kwargs)
|
||||
def collect_company_accounting_data(cls, company):
|
||||
company_data = super().collect_company_accounting_data(company)
|
||||
|
||||
# Create stock config.
|
||||
company_data.update({
|
||||
'default_account_stock_in': cls.env['account.account'].create({
|
||||
'name': 'default_account_stock_in',
|
||||
'code': 'STOCKIN',
|
||||
'reconcile': True,
|
||||
'account_type': 'asset_current',
|
||||
'company_id': company_data['company'].id,
|
||||
}),
|
||||
'default_account_stock_out': cls.env['account.account'].create({
|
||||
'name': 'default_account_stock_out',
|
||||
'code': 'STOCKOUT',
|
||||
'reconcile': True,
|
||||
'account_type': 'asset_current',
|
||||
'company_id': company_data['company'].id,
|
||||
}),
|
||||
'default_account_stock_valuation': cls.env['account.account'].create({
|
||||
'default_account_stock_valuation': cls.env['account.account'].with_company(company).create({
|
||||
'name': 'default_account_stock_valuation',
|
||||
'code': 'STOCKVAL',
|
||||
'reconcile': True,
|
||||
'account_type': 'asset_current',
|
||||
'company_id': company_data['company'].id,
|
||||
}),
|
||||
'default_warehouse': cls.env['stock.warehouse'].search(
|
||||
[('company_id', '=', company_data['company'].id)],
|
||||
[('company_id', '=', company.id)],
|
||||
limit=1,
|
||||
),
|
||||
})
|
||||
return company_data
|
||||
|
||||
def check_reconciliation(self, invoice, picking, full_reconcile=True, operation='purchase'):
|
||||
interim_account_id = self.company_data['default_account_stock_in'].id if operation == 'purchase' else self.company_data['default_account_stock_out'].id
|
||||
invoice_line = invoice.line_ids.filtered(lambda line: line.account_id.id == interim_account_id)
|
||||
|
||||
stock_moves = picking.move_ids
|
||||
|
||||
valuation_line = stock_moves.mapped('account_move_ids.line_ids').filtered(lambda x: x.account_id.id == interim_account_id)
|
||||
|
||||
if invoice.is_purchase_document() and any(l.display_type == 'cogs' for l in invoice_line):
|
||||
self.assertEqual(len(invoice_line), 2, "Only two line2 should have been written by invoice in stock input account")
|
||||
self.assertTrue(all(vl.reconciled for vl in valuation_line) or invoice_line[0].reconciled or invoice_line[1].reconciled, "The valuation and invoice line should have been reconciled together.")
|
||||
else:
|
||||
self.assertEqual(len(invoice_line), 1, "Only one line should have been written by invoice in stock input account")
|
||||
self.assertTrue(all(vl.reconciled for vl in valuation_line) or invoice_line.reconciled, "The valuation and invoice line should have been reconciled together.")
|
||||
|
||||
if invoice.move_type not in ('out_refund', 'in_refund'):
|
||||
# self.assertEqual(len(valuation_line), 1, "Only one line should have been written for stock valuation in stock input account")
|
||||
|
||||
if full_reconcile:
|
||||
self.assertTrue(all(vl.full_reconcile_id for vl in valuation_line), "The reconciliation should be total at that point.")
|
||||
else:
|
||||
self.assertFalse(all(vl.full_reconcile_id for vl in valuation_line), "The reconciliation should not be total at that point.")
|
||||
|
||||
def _process_pickings(self, pickings, date=False, quantity=False):
|
||||
|
||||
def do_picking():
|
||||
pickings.action_confirm()
|
||||
pickings.action_assign()
|
||||
for picking in pickings:
|
||||
for ml in picking.move_line_ids:
|
||||
ml.qty_done = quantity or ml.reserved_qty
|
||||
if quantity:
|
||||
for picking in pickings:
|
||||
for ml in picking.move_line_ids:
|
||||
ml.quantity = quantity
|
||||
pickings.move_ids.picked = True
|
||||
pickings._action_done()
|
||||
|
||||
if not date:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,590 @@
|
|||
from datetime import timedelta
|
||||
from freezegun import freeze_time
|
||||
|
||||
from odoo.addons.stock_account.tests.common import TestStockValuationCommon
|
||||
from odoo import fields
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests import users
|
||||
from odoo import Command
|
||||
|
||||
|
||||
class TestLotValuation(TestStockValuationCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.product = cls.product_avco.create({
|
||||
**cls.product_common_vals,
|
||||
'name': 'Lot Valuated Product',
|
||||
'categ_id': cls.category_avco.id,
|
||||
'lot_valuated': True,
|
||||
'tracking': 'lot',
|
||||
'standard_price': 10,
|
||||
})
|
||||
cls.lot1, cls.lot2, cls.lot3 = cls.env['stock.lot'].create([
|
||||
{'name': 'lot1', 'product_id': cls.product.id},
|
||||
{'name': 'lot2', 'product_id': cls.product.id},
|
||||
{'name': 'lot3', 'product_id': cls.product.id},
|
||||
])
|
||||
|
||||
def test_lot_normal_1(self):
|
||||
""" Lots have their own valuation """
|
||||
self._make_in_move(self.product, 10, 5, lot_ids=[self.lot1, self.lot2])
|
||||
self._make_in_move(self.product, 10, 7, lot_ids=[self.lot3])
|
||||
self.assertAlmostEqual(self.product.standard_price, 6.0)
|
||||
self.assertEqual(self.lot1.standard_price, 5)
|
||||
self._make_out_move(self.product, 2, lot_ids=[self.lot1])
|
||||
|
||||
# lot1 has a cost different than the product it self. So a out move should recompute the
|
||||
# product cost
|
||||
self.assertAlmostEqual(self.product.standard_price, 6.1111111, places=2) # 110 % 18 = 6.1111111
|
||||
self.assertEqual(self.lot1.total_value, 15)
|
||||
self.assertEqual(self.lot1.product_qty, 3)
|
||||
self.assertEqual(self.lot1.standard_price, 5)
|
||||
quant = self.lot1.quant_ids.filtered(lambda q: q.location_id.usage == 'internal')
|
||||
self.assertEqual(quant.value, 15)
|
||||
self.assertEqual(self.lot2.total_value, 25)
|
||||
self.assertEqual(self.lot2.product_qty, 5)
|
||||
self.assertEqual(self.lot2.standard_price, 5)
|
||||
quant = self.lot2.quant_ids.filtered(lambda q: q.location_id.usage == 'internal')
|
||||
self.assertEqual(quant.value, 25)
|
||||
self.assertEqual(self.lot3.total_value, 70)
|
||||
self.assertEqual(self.lot3.product_qty, 10)
|
||||
self.assertEqual(self.lot3.standard_price, 7)
|
||||
quant = self.lot3.quant_ids.filtered(lambda q: q.location_id.usage == 'internal')
|
||||
self.assertEqual(quant.value, 70)
|
||||
|
||||
def test_lot_normal_2(self):
|
||||
""" Lot standard_price is set at creation (not at delivery) """
|
||||
self._make_in_move(self.product, 10, 5, lot_ids=[self.lot1, self.lot2])
|
||||
out_move = self._make_out_move(self.product, 2, lot_ids=[self.lot3])
|
||||
|
||||
# lot3.standard_price was set to product.standard_price at lot creation (= 10)
|
||||
# The out move uses lot3.standard_price = 10, not the current product price
|
||||
self.assertEqual(self.product.qty_available, 8)
|
||||
self.assertEqual(self.lot3.product_qty, -2)
|
||||
self.assertEqual(out_move.value / out_move.quantity, 10)
|
||||
|
||||
def test_lot_normal_3(self):
|
||||
""" Test lot valuation and dropship"""
|
||||
self._make_dropship_move(self.product, 10, 5, lot_ids=[self.lot1, self.lot2])
|
||||
|
||||
# Dropship: product goes supplier->customer (not via stock)
|
||||
# Net effect: lots have 0 stock, so total_value = 0
|
||||
self.assertEqual(self.lot1.total_value, 0)
|
||||
self.assertEqual(self.lot1.standard_price, 5)
|
||||
self.assertEqual(self.lot2.total_value, 0)
|
||||
self.assertEqual(self.lot2.standard_price, 5)
|
||||
self.assertEqual(self.product.total_value, 0)
|
||||
|
||||
def test_real_time_valuation(self):
|
||||
""" Test account move lines for real_time valuation with lot_valuated """
|
||||
self.product.categ_id = self.category_avco_auto
|
||||
self._use_inventory_location_accounting()
|
||||
|
||||
in_move1 = self._make_in_move(self.product, 10, 5, lot_ids=[self.lot1, self.lot2], location_id=self.inventory_location.id)
|
||||
in_move2 = self._make_in_move(self.product, 10, 7, lot_ids=[self.lot3], location_id=self.inventory_location.id)
|
||||
out_move = self._make_out_move(self.product, 2, lot_ids=[self.lot1], location_dest_id=self.inventory_location.id)
|
||||
|
||||
# in_move1: 10u@5 = 50 total
|
||||
self.assertRecordValues(in_move1.account_move_id.line_ids, [
|
||||
{'debit': 0.0, 'credit': 50.0},
|
||||
{'debit': 50.0, 'credit': 0.0},
|
||||
])
|
||||
# in_move2: 10u@7 = 70 total
|
||||
self.assertRecordValues(in_move2.account_move_id.line_ids, [
|
||||
{'debit': 0.0, 'credit': 70.0},
|
||||
{'debit': 70.0, 'credit': 0.0},
|
||||
])
|
||||
# out_move: 2u of lot1@5 = 10 total
|
||||
self.assertRecordValues(out_move.account_move_id.line_ids, [
|
||||
{'debit': 0.0, 'credit': 10.0},
|
||||
{'debit': 10.0, 'credit': 0.0},
|
||||
])
|
||||
|
||||
def test_disable_lot_valuation(self):
|
||||
""" Disabling lot valuation: product valuation unchanged, lot values go to 0.
|
||||
product valuation is standard """
|
||||
self.product.product_tmpl_id.categ_id.property_cost_method = 'standard'
|
||||
self.product.product_tmpl_id.standard_price = 10
|
||||
|
||||
self._make_in_move(self.product, 10, 5, lot_ids=[self.lot1, self.lot2])
|
||||
self._make_in_move(self.product, 10, 7, lot_ids=[self.lot3])
|
||||
self._make_out_move(self.product, 2, lot_ids=[self.lot1])
|
||||
self._make_out_move(self.product, 2, lot_ids=[self.lot3])
|
||||
self._make_in_move(self.product, 9, 8, lot_ids=[self.lot1, self.lot2, self.lot3])
|
||||
|
||||
self.assertEqual(self.product.total_value, 250)
|
||||
self.assertEqual(self.product.qty_available, 25)
|
||||
self.assertEqual(self.lot1.total_value, 60)
|
||||
self.assertEqual(self.lot1.product_qty, 6)
|
||||
self.assertEqual(self.lot2.total_value, 80)
|
||||
self.assertEqual(self.lot2.product_qty, 8)
|
||||
self.assertEqual(self.lot3.total_value, 110)
|
||||
self.assertEqual(self.lot3.product_qty, 11)
|
||||
|
||||
self.product.product_tmpl_id.lot_valuated = False
|
||||
|
||||
self.assertEqual(self.product.total_value, 250)
|
||||
self.assertEqual(self.product.qty_available, 25)
|
||||
self.assertEqual(self.lot1.total_value, 0)
|
||||
self.assertEqual(self.lot2.total_value, 0)
|
||||
self.assertEqual(self.lot3.total_value, 0)
|
||||
|
||||
def test_enable_lot_valuation(self):
|
||||
""" Enabling lot valuation should compute lot values from existing stock.
|
||||
product valuation is standard """
|
||||
self.product.product_tmpl_id.categ_id.property_cost_method = 'standard'
|
||||
self.product.product_tmpl_id.standard_price = 10
|
||||
|
||||
self.product.lot_valuated = False
|
||||
|
||||
self._make_in_move(self.product, 10, 5, lot_ids=[self.lot1, self.lot2])
|
||||
self._make_in_move(self.product, 10, 7, lot_ids=[self.lot3])
|
||||
self._make_out_move(self.product, 2, lot_ids=[self.lot1])
|
||||
self._make_out_move(self.product, 2, lot_ids=[self.lot3])
|
||||
self._make_in_move(self.product, 9, 8, lot_ids=[self.lot1, self.lot2, self.lot3])
|
||||
|
||||
self.assertEqual(self.product.total_value, 250)
|
||||
self.assertEqual(self.product.qty_available, 25)
|
||||
self.assertEqual(self.lot1.total_value, 0)
|
||||
self.assertEqual(self.lot2.total_value, 0)
|
||||
self.assertEqual(self.lot3.total_value, 0)
|
||||
|
||||
self.product.product_tmpl_id.lot_valuated = True
|
||||
|
||||
self.assertEqual(self.product.total_value, 250)
|
||||
self.assertEqual(self.product.qty_available, 25)
|
||||
self.assertEqual(self.lot1.total_value, 60)
|
||||
self.assertEqual(self.lot1.product_qty, 6)
|
||||
self.assertEqual(self.lot2.total_value, 80)
|
||||
self.assertEqual(self.lot2.product_qty, 8)
|
||||
self.assertEqual(self.lot3.total_value, 110)
|
||||
self.assertEqual(self.lot3.product_qty, 11)
|
||||
|
||||
def test_enable_lot_valuation_variant(self):
|
||||
""" test enabling the lot valuation for template with multiple variant"""
|
||||
self.size_attribute = self.env['product.attribute'].create({
|
||||
'name': 'Size',
|
||||
'value_ids': [
|
||||
Command.create({'name': 'S'}),
|
||||
Command.create({'name': 'M'}),
|
||||
Command.create({'name': 'L'}),
|
||||
]
|
||||
})
|
||||
template = self.env['product.template'].create({
|
||||
'name': 'Sofa',
|
||||
'tracking': 'lot',
|
||||
'is_storable': True,
|
||||
'uom_id': self.uom.id,
|
||||
'categ_id': self.category_avco.id,
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
'attribute_id': self.size_attribute.id,
|
||||
'value_ids': [
|
||||
Command.link(self.size_attribute.value_ids[0].id),
|
||||
Command.link(self.size_attribute.value_ids[1].id),
|
||||
]}),
|
||||
],
|
||||
})
|
||||
productA, productB = template.product_variant_ids
|
||||
lotA_1, lotA_2, lotB_1, lotB_2 = self.env['stock.lot'].create([
|
||||
{'name': 'lot1', 'product_id': productA.id},
|
||||
{'name': 'lot2', 'product_id': productA.id},
|
||||
{'name': 'lot1', 'product_id': productB.id},
|
||||
{'name': 'lot2', 'product_id': productB.id},
|
||||
])
|
||||
self._make_in_move(productA, 10, 5, lot_ids=[lotA_1, lotA_2])
|
||||
self._make_in_move(productA, 10, 7, lot_ids=[lotA_2])
|
||||
self._make_in_move(productB, 10, 4, lot_ids=[lotB_1, lotB_2])
|
||||
self._make_in_move(productB, 10, 8, lot_ids=[lotB_2])
|
||||
# productA = 20u 120, productB = 20u 120
|
||||
# A_1 = 5u 25, A_2 = 15u 95, B_1 =5u 20, B_2 =15u 100
|
||||
self._make_out_move(productA, 2, lot_ids=[lotA_1, lotA_2])
|
||||
self._make_out_move(productB, 4, lot_ids=[lotB_1, lotB_2])
|
||||
# productA = 18u 108, productB = 16u 96
|
||||
# A_1 = 4u 20, A_2 = 14u 88.67, B_1 =3u 12, B_2 =13u 86.67
|
||||
self._make_in_move(productA, 6, 8, lot_ids=[lotA_1, lotA_2])
|
||||
self._make_in_move(productB, 6, 8, lot_ids=[lotB_1, lotB_2])
|
||||
# productA = 24u 156, productB = 22u 144
|
||||
# A_1 = 7u 44, A_2 = 17u 112.67, B_1 =6u 36, B_2 =16u 110.67
|
||||
|
||||
self.assertEqual(productA.total_value, 156)
|
||||
self.assertEqual(productA.qty_available, 24)
|
||||
self.assertEqual(productB.total_value, 144)
|
||||
self.assertEqual(productB.qty_available, 22)
|
||||
|
||||
template.lot_valuated = True
|
||||
|
||||
# product totals are now sum of lot totals
|
||||
self.assertEqual(productA.total_value, 156.67)
|
||||
self.assertEqual(productA.qty_available, 24)
|
||||
self.assertEqual(productB.total_value, 146.67)
|
||||
self.assertEqual(productB.qty_available, 22)
|
||||
|
||||
# Lot values computed via lot-specific AVCO
|
||||
self.assertEqual(lotA_1.product_qty, 7)
|
||||
self.assertEqual(lotA_1.total_value, 44)
|
||||
self.assertEqual(lotA_2.product_qty, 17)
|
||||
self.assertEqual(lotA_2.total_value, 112.67)
|
||||
self.assertEqual(lotB_1.product_qty, 6)
|
||||
self.assertEqual(lotB_1.total_value, 36)
|
||||
self.assertEqual(lotB_2.product_qty, 16)
|
||||
self.assertEqual(lotB_2.total_value, 110.67)
|
||||
|
||||
def test_enforce_lot_receipt(self):
|
||||
""" lot/sn is mandatory on receipt if the product is lot valuated """
|
||||
self.picking_type_in.use_create_lots = False
|
||||
with self.assertRaises(UserError):
|
||||
self._make_in_move(self.product, 10, 5)
|
||||
|
||||
def test_enforce_lot_inventory(self):
|
||||
""" lot/sn is mandatory on quant if the product is lot valuated """
|
||||
inventory_quant = self.env['stock.quant'].create({
|
||||
'location_id': self.stock_location.id,
|
||||
'product_id': self.product.id,
|
||||
'inventory_quantity': 10
|
||||
})
|
||||
with self.assertRaises(UserError):
|
||||
inventory_quant.action_apply_inventory()
|
||||
|
||||
def test_inventory_adjustment_existing_lot(self):
|
||||
""" If a lot exist, inventory takes its cost, if not, takes standard price """
|
||||
self.product.product_tmpl_id.standard_price = 10
|
||||
shelf1 = self.env['stock.location'].create({
|
||||
'name': 'Shelf 1',
|
||||
'usage': 'internal',
|
||||
'location_id': self.stock_location.id,
|
||||
})
|
||||
self._make_in_move(self.product, 10, 5, lot_ids=[self.lot1])
|
||||
inventory_quant = self.env['stock.quant'].create({
|
||||
'location_id': shelf1.id,
|
||||
'product_id': self.product.id,
|
||||
'lot_id': self.lot1.id,
|
||||
'inventory_quantity': 1
|
||||
})
|
||||
|
||||
inventory_quant.action_apply_inventory()
|
||||
# lot1 now has 11u at standard_price=5 (from the in move)
|
||||
self.assertEqual(self.lot1.standard_price, 5)
|
||||
self.assertEqual(self.lot1.product_qty, 11)
|
||||
self.assertEqual(self.lot1.total_value, 55)
|
||||
|
||||
def test_inventory_adjustment_new_lot(self):
|
||||
""" If a lot exist, inventory takes its cost, if not, takes standard price """
|
||||
shelf1 = self.env['stock.location'].create({
|
||||
'name': 'Shelf 1',
|
||||
'usage': 'internal',
|
||||
'location_id': self.stock_location.id,
|
||||
})
|
||||
self._make_in_move(self.product, 10, 5, lot_ids=[self.lot1])
|
||||
self._make_in_move(self.product, 10, 9, lot_ids=[self.lot2])
|
||||
self.assertEqual(self.product.standard_price, 7)
|
||||
lot4 = self.env['stock.lot'].create({
|
||||
'name': 'lot4',
|
||||
'product_id': self.product.id,
|
||||
})
|
||||
inventory_quant = self.env['stock.quant'].create({
|
||||
'location_id': shelf1.id,
|
||||
'product_id': self.product.id,
|
||||
'lot_id': lot4.id,
|
||||
'inventory_quantity': 1,
|
||||
})
|
||||
|
||||
inventory_quant.action_apply_inventory()
|
||||
# lot4 was created when product.standard_price = 7
|
||||
self.assertEqual(lot4.standard_price, 7)
|
||||
self.assertEqual(lot4.product_qty, 1)
|
||||
self.assertEqual(lot4.total_value, 7)
|
||||
|
||||
def test_change_standard_price(self):
|
||||
""" Changing product's standard price will reevaluate all lots """
|
||||
self._make_in_move(self.product, 10, 5, lot_ids=[self.lot1, self.lot2])
|
||||
self._make_in_move(self.product, 8, 7, lot_ids=[self.lot3])
|
||||
self._make_in_move(self.product, 6, 8, lot_ids=[self.lot2, self.lot3])
|
||||
self.assertEqual(self.lot1.total_value, 25)
|
||||
self.assertEqual(self.lot2.total_value, 49)
|
||||
self.assertEqual(self.lot3.total_value, 80)
|
||||
self.product.product_tmpl_id.standard_price = 10
|
||||
|
||||
self.assertEqual(self.lot1.total_value, 50)
|
||||
self.assertEqual(self.lot1.standard_price, 10)
|
||||
self.assertEqual(self.lot2.total_value, 80)
|
||||
self.assertEqual(self.lot2.standard_price, 10)
|
||||
self.assertEqual(self.lot3.total_value, 110)
|
||||
self.assertEqual(self.lot3.standard_price, 10)
|
||||
|
||||
def test_value_multicompanies(self):
|
||||
""" Test having multiple layers on different companies give a correct value"""
|
||||
c1 = self.company
|
||||
c2 = self.other_company
|
||||
self.product.product_tmpl_id.with_company(c2).categ_id.property_cost_method = 'average'
|
||||
# c1 moves
|
||||
self._make_in_move(self.product, 10, 5, lot_ids=[self.lot1, self.lot2])
|
||||
self._make_in_move(self.product, 8, 7, lot_ids=[self.lot3])
|
||||
self._make_in_move(self.product, 6, 8, lot_ids=[self.lot2, self.lot3])
|
||||
# c2 move
|
||||
self._make_in_move(self.product, 9, 6, company=c2, lot_ids=[self.lot1, self.lot2, self.lot3])
|
||||
self.assertEqual(self.lot1.with_company(c1).total_value, 25)
|
||||
self.assertEqual(self.lot2.with_company(c1).total_value, 49)
|
||||
self.assertEqual(self.lot3.with_company(c1).total_value, 80)
|
||||
self.assertEqual(self.lot1.with_company(c2).total_value, 18)
|
||||
self.assertEqual(self.lot2.with_company(c2).total_value, 18)
|
||||
self.assertEqual(self.lot3.with_company(c2).total_value, 18)
|
||||
|
||||
def test_change_cost_method(self):
|
||||
""" Prevent changing cost method if lot valuated """
|
||||
# change cost method on category
|
||||
self._make_in_move(self.product, 1, 5, lot_ids=[self.lot1])
|
||||
self._make_in_move(self.product, 1, 7, lot_ids=[self.lot1])
|
||||
self._make_out_move(self.product, 1, lot_ids=[self.lot1])
|
||||
self.assertEqual(self.lot1.total_value, 6)
|
||||
|
||||
self.product.categ_id = self.category_fifo
|
||||
self.assertEqual(self.lot1.total_value, 7)
|
||||
|
||||
self.product.categ_id.property_cost_method = 'average'
|
||||
self.assertEqual(self.lot1.total_value, 6)
|
||||
|
||||
def test_change_lot_cost(self):
|
||||
""" Changing the cost of a lot will reevaluate the lot """
|
||||
self._make_in_move(self.product, 10, 5, lot_ids=[self.lot1, self.lot2])
|
||||
self._make_in_move(self.product, 10, 7, lot_ids=[self.lot3])
|
||||
self._make_out_move(self.product, 2, lot_ids=[self.lot1])
|
||||
self.assertAlmostEqual(self.product.standard_price, (15 + 25 + 70) / 18, places=2)
|
||||
self.lot1.standard_price = 10
|
||||
self.assertEqual(self.lot1.total_value, 30)
|
||||
self.assertEqual(self.lot1.product_qty, 3)
|
||||
self.assertEqual(self.lot1.standard_price, 10)
|
||||
# product cost should be updated as well
|
||||
self.assertAlmostEqual(self.product.standard_price, (30 + 25 + 70) / 18, places=2)
|
||||
# rest remains unchanged
|
||||
self.assertEqual(self.lot2.total_value, 25)
|
||||
self.assertEqual(self.lot2.product_qty, 5)
|
||||
self.assertEqual(self.lot2.standard_price, 5)
|
||||
self.assertEqual(self.lot3.total_value, 70)
|
||||
self.assertEqual(self.lot3.product_qty, 10)
|
||||
self.assertEqual(self.lot3.standard_price, 7)
|
||||
|
||||
def test_lot_move_update_after_done(self):
|
||||
"""validate a stock move. Edit the move line in done state."""
|
||||
move = self._make_in_move(self.product, 8, 5, create_picking=True, lot_ids=[self.lot1, self.lot2])
|
||||
move.picking_id.action_toggle_is_locked()
|
||||
# 4 lot 1, 6 lot 2 and 3 lot 3
|
||||
move.move_line_ids = [
|
||||
Command.update(move.move_line_ids[1].id, {'quantity': 6}),
|
||||
Command.create({
|
||||
'product_id': self.product.id,
|
||||
'product_uom_id': self.product.uom_id.id,
|
||||
'quantity': 3,
|
||||
'lot_id': self.lot3.id,
|
||||
}),
|
||||
]
|
||||
move.value_manual = 13 * 5 # Small trick to simulate move revaluation
|
||||
self.assertEqual(self.lot1.product_qty, 4)
|
||||
self.assertEqual(self.lot2.product_qty, 6)
|
||||
self.assertEqual(self.lot3.product_qty, 3)
|
||||
self.assertEqual(self.lot1.total_value, 4 * 5)
|
||||
self.assertEqual(self.lot2.total_value, 6 * 5)
|
||||
self.assertEqual(self.lot3.total_value, 3 * 5)
|
||||
|
||||
def test_lot_average_vacuum(self):
|
||||
""" Test lot AVCO with negative stock fill """
|
||||
with freeze_time(fields.Datetime.now() - timedelta(seconds=10)):
|
||||
self.product.standard_price = 9
|
||||
self._make_out_move(self.product, 2, lot_ids=[self.lot1])
|
||||
self._make_out_move(self.product, 3, lot_ids=[self.lot2])
|
||||
self._make_in_move(self.product, 10, 7, lot_ids=[self.lot3])
|
||||
|
||||
self.assertEqual(self.lot3.standard_price, 7)
|
||||
self._make_in_move(self.product, 10, 5, lot_ids=[self.lot1, self.lot2])
|
||||
self.assertEqual(self.lot1.standard_price, 5)
|
||||
self.assertEqual(self.lot3.standard_price, 7)
|
||||
|
||||
def test_return_lot_valuated(self):
|
||||
with freeze_time(fields.Datetime.now() - timedelta(seconds=10)):
|
||||
self.product.standard_price = 9
|
||||
move = self._make_out_move(self.product, 3, create_picking=True, lot_ids=[self.lot1, self.lot2, self.lot3])
|
||||
self.assertEqual(self.product.total_value, -27)
|
||||
self.assertEqual(move.value, 27)
|
||||
return_move = self._make_return(move, 2)
|
||||
self.assertEqual(return_move.state, 'done')
|
||||
# Return move has positive value (in move restoring 2 lots)
|
||||
self.assertEqual(return_move.value, 18)
|
||||
self.assertEqual(self.product.total_value, -9)
|
||||
|
||||
def test_lot_inventory(self):
|
||||
"""Test setting quantity for a new lot via inventory adjustment fallback on the product cost
|
||||
The product is set to avco cost """
|
||||
self.product.standard_price = 9
|
||||
lot = self.env['stock.lot'].create({
|
||||
'product_id': self.product.id,
|
||||
'name': 'test',
|
||||
})
|
||||
quant = self.env['stock.quant'].create({
|
||||
'product_id': self.product.id,
|
||||
'lot_id': lot.id,
|
||||
'location_id': self.stock_location.id,
|
||||
'inventory_quantity': 3
|
||||
})
|
||||
quant.action_apply_inventory()
|
||||
self.assertEqual(lot.standard_price, 9)
|
||||
self.assertEqual(lot.total_value, 27)
|
||||
|
||||
def test_lot_valuation_after_tracking_update(self):
|
||||
"""
|
||||
Test that 'lot_valuated' is set to False when the tracking is changed to 'none'.
|
||||
"""
|
||||
# update the tracking from product.product
|
||||
self.assertEqual(self.product.tracking, 'lot')
|
||||
self.product.lot_valuated = True
|
||||
self.assertTrue(self.product.lot_valuated)
|
||||
self.product.tracking = 'none'
|
||||
self.assertFalse(self.product.lot_valuated)
|
||||
# update the tracking from product.template
|
||||
self.product.tracking = 'lot'
|
||||
self.product.lot_valuated = True
|
||||
self.product.product_tmpl_id.tracking = 'none'
|
||||
self.assertFalse(self.product.lot_valuated)
|
||||
|
||||
def test_lot_valuation_lot_product_price_diff(self):
|
||||
"""
|
||||
This test ensure that when the product.standard_price and the lot.standard_price differ,
|
||||
no discrepancy is created when setting lot_valuated to True.
|
||||
When lot_valuated is set to True, the lot.standard_price is updated to match with the product.standard_price
|
||||
"""
|
||||
self.product.lot_valuated = False
|
||||
self.product.standard_price = 1
|
||||
|
||||
lot = self.env['stock.lot'].create({
|
||||
'product_id': self.product.id,
|
||||
'name': 'LOT-WITH-COST',
|
||||
'standard_price': 2,
|
||||
})
|
||||
lot2 = self.env['stock.lot'].create({
|
||||
'product_id': self.product.id,
|
||||
'name': 'LOT-NO-COST',
|
||||
})
|
||||
quant = self.env['stock.quant'].create({
|
||||
'product_id': self.product.id,
|
||||
'lot_id': lot.id,
|
||||
'location_id': self.stock_location.id,
|
||||
'inventory_quantity': 10,
|
||||
})
|
||||
quant.action_apply_inventory()
|
||||
|
||||
self.assertEqual(self.product.total_value, 10) # 10 units with product standard_price = $1
|
||||
self.assertEqual(lot.standard_price, 2)
|
||||
self.assertEqual(lot2.standard_price, 0)
|
||||
|
||||
self.product.lot_valuated = True
|
||||
|
||||
self.assertEqual(lot2.standard_price, 1)
|
||||
self.assertEqual(lot.standard_price, 1) # lot.standard_price was updated
|
||||
self.assertEqual(lot.total_value, 10)
|
||||
|
||||
quant.inventory_quantity = 0
|
||||
quant.action_apply_inventory()
|
||||
|
||||
self.assertEqual(lot.total_value, 0)
|
||||
|
||||
def test_lot_valuated_update_from_product_product(self):
|
||||
tmpl1 = self.product.product_tmpl_id
|
||||
tmpl1.standard_price = 1
|
||||
tmpl1.tracking = 'lot'
|
||||
tmpl1.lot_valuated = False
|
||||
|
||||
lot = self.env['stock.lot'].create({
|
||||
'product_id': self.product.id,
|
||||
'name': 'test',
|
||||
})
|
||||
quant = self.env['stock.quant'].create({
|
||||
'product_id': self.product.id,
|
||||
'lot_id': lot.id,
|
||||
'location_id': self.stock_location.id,
|
||||
'inventory_quantity': 1
|
||||
})
|
||||
quant.action_apply_inventory()
|
||||
|
||||
self.assertEqual(self.product.qty_available, 1)
|
||||
self.assertEqual(self.product.total_value, 1)
|
||||
self.assertEqual(lot.product_qty, 1) # physical qty always reflects stock, regardless of lot_valuated
|
||||
self.assertEqual(lot.total_value, 0)
|
||||
|
||||
self.product.lot_valuated = True # The update is done from the ProductProduct model
|
||||
self.env.cr.flush()
|
||||
self.assertEqual(lot.product_qty, 1)
|
||||
self.assertEqual(lot.total_value, 1)
|
||||
self.assertEqual(self.product.qty_available, 1)
|
||||
self.assertEqual(self.product.total_value, 1)
|
||||
|
||||
self.product.lot_valuated = False # Check that
|
||||
self.env.cr.flush()
|
||||
|
||||
self.assertEqual(self.product.qty_available, 1)
|
||||
self.assertEqual(self.product.total_value, 1)
|
||||
self.assertEqual(lot.product_qty, 1) # physical qty unchanged, only valuation is cleared
|
||||
self.assertEqual(lot.total_value, 0)
|
||||
|
||||
def test_no_lot_valuation_if_quant_without_lot(self):
|
||||
""" Ensure that it is not possible to set lot_valuated to True
|
||||
if there is valued quantities without lot in on hand.
|
||||
This is because you can't validate a move without lot when lot valuation is enabled.
|
||||
The user would hence be unable to use the quant without lot anyway.
|
||||
"""
|
||||
self.product.tracking = 'none'
|
||||
self.product.lot_valuated = False
|
||||
quant = self.env['stock.quant'].create({
|
||||
'product_id': self.product.id,
|
||||
'location_id': self.stock_location.id,
|
||||
'inventory_quantity': 1
|
||||
})
|
||||
quant.action_apply_inventory()
|
||||
|
||||
self.product.tracking = 'lot'
|
||||
with self.assertRaises(UserError):
|
||||
self.product.lot_valuated = True
|
||||
|
||||
def test_lot_revaluation_with_remaining_qty(self):
|
||||
"""
|
||||
Test manual lot revaluation: setting lot.standard_price updates total_value.
|
||||
After disabling lot_valuated, lot total_value becomes 0.
|
||||
"""
|
||||
self._make_in_move(self.product, 7, lot_ids=[self.lot1])
|
||||
|
||||
# lot1 has stock; setting standard_price updates total_value
|
||||
self.lot1.standard_price = 15
|
||||
self.assertEqual(self.lot1.standard_price, 15)
|
||||
self.assertEqual(self.lot1.total_value, 7 * 15)
|
||||
|
||||
# After disabling lot_valuated, lot total_value = 0
|
||||
self.product.lot_valuated = False
|
||||
self.assertEqual(self.lot1.total_value, 0)
|
||||
self.assertGreater(self.product.total_value, 0)
|
||||
|
||||
@users('inventory_user')
|
||||
def test_deliveries_with_minimal_access_rights(self):
|
||||
""" Check that an inventory user is able to process a delivery. """
|
||||
move = self._make_out_move(self.product, 5, create_picking=True, lot_ids=[self.lot1])
|
||||
delivery = move.picking_id
|
||||
self.assertEqual(delivery.state, 'done')
|
||||
self.assertRecordValues(delivery.move_ids, [
|
||||
{'quantity': 5.0, 'state': 'done', 'lot_ids': self.lot1.ids}
|
||||
])
|
||||
|
||||
def test_in_move_lot_valuated_standard_price(self):
|
||||
"""Check that when the standard price is used to value a move
|
||||
with a single lot, the standard price of the lot is used instead of the
|
||||
standard price of the product
|
||||
"""
|
||||
self.product.categ_id.property_valuation = 'real_time'
|
||||
self._make_in_move(self.product, 1, 10, lot_ids=[self.lot1])
|
||||
self._make_in_move(self.product, 1, 16, lot_ids=[self.lot2])
|
||||
self.assertEqual(self.product.standard_price, 13)
|
||||
self.assertEqual(self.lot1.standard_price, 10)
|
||||
self.assertEqual(self.lot2.standard_price, 16)
|
||||
|
||||
# Second receipt for lot1 at same cost: lot price unchanged, product AVCO recalculated
|
||||
self._make_in_move(self.product, 1, 10, lot_ids=[self.lot1])
|
||||
self.assertEqual(self.lot1.standard_price, 10)
|
||||
self.assertEqual(self.product.standard_price, 12)
|
||||
|
|
@ -1,239 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests import Form
|
||||
from odoo.addons.stock_account.tests.test_stockvaluation import _create_accounting_data
|
||||
from odoo.addons.stock_account.tests.test_stockvaluationlayer import TestStockValuationCommon
|
||||
|
||||
|
||||
class TestStockValuationLayerRevaluation(TestStockValuationCommon):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestStockValuationLayerRevaluation, cls).setUpClass()
|
||||
cls.stock_input_account, cls.stock_output_account, cls.stock_valuation_account, cls.expense_account, cls.stock_journal = _create_accounting_data(cls.env)
|
||||
cls.product1.write({
|
||||
'property_account_expense_id': cls.expense_account.id,
|
||||
})
|
||||
cls.product1.categ_id.write({
|
||||
'property_valuation': 'real_time',
|
||||
'property_stock_account_input_categ_id': cls.stock_input_account.id,
|
||||
'property_stock_account_output_categ_id': cls.stock_output_account.id,
|
||||
'property_stock_valuation_account_id': cls.stock_valuation_account.id,
|
||||
'property_stock_journal': cls.stock_journal.id,
|
||||
})
|
||||
|
||||
cls.product1.categ_id.property_valuation = 'real_time'
|
||||
|
||||
def test_stock_valuation_layer_revaluation_avco(self):
|
||||
self.product1.categ_id.property_cost_method = 'average'
|
||||
context = {
|
||||
'default_product_id': self.product1.id,
|
||||
'default_company_id': self.env.company.id,
|
||||
'default_added_value': 0.0
|
||||
}
|
||||
# Quantity of product1 is zero, raise
|
||||
with self.assertRaises(UserError):
|
||||
Form(self.env['stock.valuation.layer.revaluation'].with_context(context)).save()
|
||||
|
||||
self._make_in_move(self.product1, 10, unit_cost=2)
|
||||
self._make_in_move(self.product1, 10, unit_cost=4)
|
||||
|
||||
self.assertEqual(self.product1.standard_price, 3)
|
||||
self.assertEqual(self.product1.quantity_svl, 20)
|
||||
|
||||
old_layers = self.env['stock.valuation.layer'].search([('product_id', '=', self.product1.id)], order="create_date desc, id desc")
|
||||
|
||||
self.assertEqual(len(old_layers), 2)
|
||||
self.assertEqual(old_layers[0].remaining_value, 40)
|
||||
|
||||
revaluation_wizard = Form(self.env['stock.valuation.layer.revaluation'].with_context(context))
|
||||
revaluation_wizard.added_value = 20
|
||||
revaluation_wizard.account_id = self.stock_valuation_account
|
||||
revaluation_wizard.save().action_validate_revaluation()
|
||||
|
||||
# Check standard price change
|
||||
self.assertEqual(self.product1.standard_price, 4)
|
||||
self.assertEqual(self.product1.quantity_svl, 20)
|
||||
|
||||
# Check the creation of stock.valuation.layer
|
||||
new_layer = self.env['stock.valuation.layer'].search([('product_id', '=', self.product1.id)], order="create_date desc, id desc", limit=1)
|
||||
self.assertEqual(new_layer.value, 20)
|
||||
|
||||
# Check the remaing value of current layers
|
||||
self.assertEqual(old_layers[0].remaining_value, 50)
|
||||
self.assertEqual(sum(slv.remaining_value for slv in old_layers), 80)
|
||||
|
||||
# Check account move
|
||||
self.assertTrue(bool(new_layer.account_move_id))
|
||||
self.assertEqual(len(new_layer.account_move_id.line_ids), 2)
|
||||
|
||||
self.assertEqual(sum(new_layer.account_move_id.line_ids.mapped("debit")), 20)
|
||||
self.assertEqual(sum(new_layer.account_move_id.line_ids.mapped("credit")), 20)
|
||||
|
||||
credit_lines = [l for l in new_layer.account_move_id.line_ids if l.credit > 0]
|
||||
self.assertEqual(len(credit_lines), 1)
|
||||
self.assertEqual(credit_lines[0].account_id.id, self.stock_valuation_account.id)
|
||||
|
||||
def test_stock_valuation_layer_revaluation_avco_rounding(self):
|
||||
self.product1.categ_id.property_cost_method = 'average'
|
||||
context = {
|
||||
'default_product_id': self.product1.id,
|
||||
'default_company_id': self.env.company.id,
|
||||
'default_added_value': 0.0
|
||||
}
|
||||
# Quantity of product1 is zero, raise
|
||||
with self.assertRaises(UserError):
|
||||
Form(self.env['stock.valuation.layer.revaluation'].with_context(context)).save()
|
||||
|
||||
self._make_in_move(self.product1, 1, unit_cost=1)
|
||||
self._make_in_move(self.product1, 1, unit_cost=1)
|
||||
self._make_in_move(self.product1, 1, unit_cost=1)
|
||||
|
||||
self.assertEqual(self.product1.standard_price, 1)
|
||||
self.assertEqual(self.product1.quantity_svl, 3)
|
||||
|
||||
old_layers = self.env['stock.valuation.layer'].search([('product_id', '=', self.product1.id)], order="create_date desc, id desc")
|
||||
|
||||
self.assertEqual(len(old_layers), 3)
|
||||
self.assertEqual(old_layers[0].remaining_value, 1)
|
||||
|
||||
revaluation_wizard = Form(self.env['stock.valuation.layer.revaluation'].with_context(context))
|
||||
revaluation_wizard.added_value = 1
|
||||
revaluation_wizard.account_id = self.stock_valuation_account
|
||||
revaluation_wizard.save().action_validate_revaluation()
|
||||
|
||||
# Check standard price change
|
||||
self.assertEqual(self.product1.standard_price, 1.33)
|
||||
self.assertEqual(self.product1.quantity_svl, 3)
|
||||
|
||||
# Check the creation of stock.valuation.layer
|
||||
new_layer = self.env['stock.valuation.layer'].search([('product_id', '=', self.product1.id)], order="create_date desc, id desc", limit=1)
|
||||
self.assertEqual(new_layer.value, 1)
|
||||
|
||||
# Check the remaing value of current layers
|
||||
self.assertEqual(sum(slv.remaining_value for slv in old_layers), 4)
|
||||
self.assertTrue(1.34 in old_layers.mapped("remaining_value"))
|
||||
|
||||
# Check account move
|
||||
self.assertTrue(bool(new_layer.account_move_id))
|
||||
self.assertEqual(len(new_layer.account_move_id.line_ids), 2)
|
||||
|
||||
self.assertEqual(sum(new_layer.account_move_id.line_ids.mapped("debit")), 1)
|
||||
self.assertEqual(sum(new_layer.account_move_id.line_ids.mapped("credit")), 1)
|
||||
|
||||
credit_lines = [l for l in new_layer.account_move_id.line_ids if l.credit > 0]
|
||||
self.assertEqual(len(credit_lines), 1)
|
||||
self.assertEqual(credit_lines[0].account_id.id, self.stock_valuation_account.id)
|
||||
|
||||
def test_stock_valuation_layer_revaluation_avco_rounding_2_digits(self):
|
||||
"""
|
||||
Check that the rounding of the new price (cost) is equivalent to the rounding of the standard price (cost)
|
||||
The check is done indirectly via the layers valuations.
|
||||
If correct => rounding method is correct too
|
||||
"""
|
||||
self.product1.categ_id.property_cost_method = 'average'
|
||||
|
||||
self.env['decimal.precision'].search([
|
||||
('name', '=', 'Product Price'),
|
||||
]).digits = 2
|
||||
self.product1.write({'standard_price': 0})
|
||||
|
||||
# First Move
|
||||
self.product1.write({'standard_price': 0.022})
|
||||
self._make_in_move(self.product1, 10000)
|
||||
|
||||
self.assertEqual(self.product1.standard_price, 0.02)
|
||||
self.assertEqual(self.product1.quantity_svl, 10000)
|
||||
|
||||
layer = self.product1.stock_valuation_layer_ids
|
||||
self.assertEqual(layer.value, 200)
|
||||
|
||||
# Second Move
|
||||
self.product1.write({'standard_price': 0.053})
|
||||
|
||||
self.assertEqual(self.product1.standard_price, 0.05)
|
||||
self.assertEqual(self.product1.quantity_svl, 10000)
|
||||
|
||||
layers = self.product1.stock_valuation_layer_ids
|
||||
self.assertEqual(layers[0].value, 200)
|
||||
self.assertEqual(layers[1].value, 300)
|
||||
|
||||
def test_stock_valuation_layer_revaluation_avco_rounding_5_digits(self):
|
||||
"""
|
||||
Check that the rounding of the new price (cost) is equivalent to the rounding of the standard price (cost)
|
||||
The check is done indirectly via the layers valuations.
|
||||
If correct => rounding method is correct too
|
||||
"""
|
||||
self.product1.categ_id.property_cost_method = 'average'
|
||||
|
||||
self.env['decimal.precision'].search([
|
||||
('name', '=', 'Product Price'),
|
||||
]).digits = 5
|
||||
|
||||
# First Move
|
||||
self.product1.write({'standard_price': 0.00875})
|
||||
self._make_in_move(self.product1, 10000)
|
||||
|
||||
self.assertEqual(self.product1.standard_price, 0.00875)
|
||||
self.assertEqual(self.product1.quantity_svl, 10000)
|
||||
|
||||
layer = self.product1.stock_valuation_layer_ids
|
||||
self.assertEqual(layer.value, 87.5)
|
||||
|
||||
# Second Move
|
||||
self.product1.write({'standard_price': 0.00975})
|
||||
|
||||
self.assertEqual(self.product1.standard_price, 0.00975)
|
||||
self.assertEqual(self.product1.quantity_svl, 10000)
|
||||
|
||||
layers = self.product1.stock_valuation_layer_ids
|
||||
self.assertEqual(layers[0].value, 87.5)
|
||||
self.assertEqual(layers[1].value, 10)
|
||||
|
||||
def test_stock_valuation_layer_revaluation_fifo(self):
|
||||
self.product1.categ_id.property_cost_method = 'fifo'
|
||||
context = {
|
||||
'default_product_id': self.product1.id,
|
||||
'default_company_id': self.env.company.id,
|
||||
'default_added_value': 0.0
|
||||
}
|
||||
# Quantity of product1 is zero, raise
|
||||
with self.assertRaises(UserError):
|
||||
Form(self.env['stock.valuation.layer.revaluation'].with_context(context)).save()
|
||||
|
||||
self._make_in_move(self.product1, 10, unit_cost=2)
|
||||
self._make_in_move(self.product1, 10, unit_cost=4)
|
||||
|
||||
self.assertEqual(self.product1.standard_price, 2)
|
||||
self.assertEqual(self.product1.quantity_svl, 20)
|
||||
|
||||
old_layers = self.env['stock.valuation.layer'].search([('product_id', '=', self.product1.id)], order="create_date desc, id desc")
|
||||
|
||||
self.assertEqual(len(old_layers), 2)
|
||||
self.assertEqual(old_layers[0].remaining_value, 40)
|
||||
|
||||
revaluation_wizard = Form(self.env['stock.valuation.layer.revaluation'].with_context(context))
|
||||
revaluation_wizard.added_value = 20
|
||||
revaluation_wizard.account_id = self.stock_valuation_account
|
||||
revaluation_wizard.save().action_validate_revaluation()
|
||||
|
||||
self.assertEqual(self.product1.standard_price, 3)
|
||||
|
||||
# Check the creation of stock.valuation.layer
|
||||
new_layer = self.env['stock.valuation.layer'].search([('product_id', '=', self.product1.id)], order="create_date desc, id desc", limit=1)
|
||||
self.assertEqual(new_layer.value, 20)
|
||||
|
||||
# Check the remaing value of current layers
|
||||
self.assertEqual(old_layers[0].remaining_value, 50)
|
||||
self.assertEqual(sum(slv.remaining_value for slv in old_layers), 80)
|
||||
|
||||
# Check account move
|
||||
self.assertTrue(bool(new_layer.account_move_id))
|
||||
self.assertTrue(len(new_layer.account_move_id.line_ids), 2)
|
||||
|
||||
self.assertEqual(sum(new_layer.account_move_id.line_ids.mapped("debit")), 20)
|
||||
self.assertEqual(sum(new_layer.account_move_id.line_ids.mapped("credit")), 20)
|
||||
|
||||
credit_lines = [l for l in new_layer.account_move_id.line_ids if l.credit > 0]
|
||||
self.assertEqual(len(credit_lines), 1)
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue