[ADD] stock_inventory_revaluation

This commit is contained in:
jbeficent
2016-02-23 14:41:27 +01:00
committed by Jordi Ballester Alomar
parent ddbd5aa61e
commit da2613d4a2
17 changed files with 1296 additions and 0 deletions

View File

@@ -0,0 +1,85 @@
.. image:: https://img.shields.io/badge/license-AGPLv3-blue.svg
:target: https://www.gnu.org/licenses/agpl.html
:alt: License: AGPL-3
===================================
Stock Account Inventory Revaluation
===================================
If your company runs a perpetual inventory system, you may need to perform
inventory revaluation.
You can re-valuate inventory values by:
* Changing the price for a specific product. The inventory price is changed
and inventory value is recalculated according to the new price. In case of
real price, you can select which quants you want to change the price on.
* Changing the value of the inventory. The quantity of inventory remains
unchanged, resulting in a change in the price.
Configuration
=============
* Go to *Inventory / Configuration / Products / Product Categories* and
define, for each category, a Valuation Increase Account and a Valuation
Decrease Account. These accounts will be used as contra-accounts to the
Stock Valuation Account during the inventory re-valuation.
* Users willing to access to the Inventory Revaluation menu should be
members of the group "Manage Inventory Valuation and Costing Methods".
Usage
=====
* Go to *Inventory / Inventory Control / Inventory Revaluation / Products*
to create a new Inventory Revaluation.
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/154/8.0
Bug Tracker
===========
Bugs are tracked on `GitHub Issues
<https://github.com/OCA/stock_account_inventory_revaluation/issues>`_. In
case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and
welcomed `feedback <https://github.com/OCA/154/issues/new?body=module:%20
stock_account_inventory_revaluation%0Aversion:%20
8.0%0A%0A**Steps%20to%20reproduce**%0A-%20..
.%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Credits
=======
Images
------
* Odoo Community Association: `Icon <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_.
Contributors
------------
* Eficent Business and IT Consulting Services S.L. <contact@eficent.com>
Maintainer
----------
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: http://odoo-community.org
This module is maintained by the OCA.
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
To contribute to this module, please visit http://odoo-community.org.

View File

@@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
# © 2015 Eficent Business and IT Consulting Services S.L.
# - Jordi Ballester Alomar
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from . import models
from . import wizards

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# © 2015 Eficent Business and IT Consulting Services S.L.
# - Jordi Ballester Alomar
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
{
"name": "Stock Inventory Revaluation",
"summary": "Introduces inventory revaluation as single point to change "
"the valuation of products.",
"version": "8.0.1.0.0",
"author": "Eficent Business and IT Consulting Services S.L., "
"Odoo Community Association (OCA)",
"website": "http://www.eficent.com",
"category": "Warehouse",
"depends": ["stock_account"],
"license": "AGPL-3",
"data": [
"wizards/stock_inventory_revaluation_get_quants_view.xml",
"security/stock_inventory_revaluation_security.xml",
"security/ir.model.access.csv",
"views/stock_inventory_revaluation_view.xml",
"views/product_view.xml",
"data/stock_inventory_revaluation_data.xml",
],
'installable': True,
}

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="1">
<record id="sequence_stock_inventory_revaluation_type"
model="ir.sequence.type">
<field name="name">Stock Inventory Revaluation</field>
<field name="code">stock.inventory.revaluation</field>
</record>
<record id="sequence_stock_inventory_revaluation" model="ir.sequence">
<field name="name">Stock Inventory Revaluation</field>
<field name="code">stock.inventory.revaluation</field>
<field name="prefix">IR/</field>
<field name="padding">5</field>
<field name="number_next">1</field>
<field name="number_increment">1</field>
</record>
</data>
</openerp>

View File

@@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
# © 2015 Eficent Business and IT Consulting Services S.L.
# - Jordi Ballester Alomar
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from . import product
from . import stock_inventory_revaluation

View File

@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# © 2015 Eficent Business and IT Consulting Services S.L.
# - Jordi Ballester Alomar
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from openerp import fields, models
class ProductCategory(models.Model):
_inherit = 'product.category'
property_inventory_revaluation_increase_account_categ = fields.Many2one(
'account.account', string='Valuation Increase Account',
company_dependent=True,
help="Define the G/L accounts to be used as the balancing account in "
"the transaction created by the revaluation. The G/L Increase "
"Account is used when the inventory value is increased due to "
"the revaluation.")
property_inventory_revaluation_decrease_account_categ = fields.Many2one(
'account.account', string='Valuation Decrease Account',
company_dependent=True,
help="Define the G/L accounts to be used as the balancing account in "
"the transaction created by the revaluation. The G/L Decrease "
"Account is used when the inventory value is decreased.")

View File

@@ -0,0 +1,450 @@
# -*- coding: utf-8 -*-
# © 2015 Eficent Business and IT Consulting Services S.L.
# - Jordi Ballester Alomar
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from openerp import api, fields, models, _
import openerp.addons.decimal_precision as dp
import time
from openerp.exceptions import Warning as UserError
_STATES = [
('draft', 'Draft'),
('posted', 'Posted'),
('cancel', 'Cancelled')]
class StockInventoryRevaluation(models.Model):
_name = 'stock.inventory.revaluation'
_description = 'Inventory revaluation'
@api.model
def _default_journal(self):
res = self.env['account.journal'].search([('type', '=', 'general')])
return res and res[0] or False
name = fields.Char('Reference',
help="Reference for the journal entry",
readonly=True,
required=True,
states={'draft': [('readonly', False)]},
copy=False,
default='/')
revaluation_type = fields.Selection(
[('price_change', 'Price Change'),
('inventory_value', 'Inventory Debit/Credit')],
string="Revaluation Type",
readonly=True, required=True,
default='price_change',
help="'Price Change': You can re-valuate inventory values by Changing "
"the price for a specific product. The inventory price is "
"changed and inventory value is recalculated according to the "
"new price.\n "
"'Inventory Debit/Credit': Changing the value of the inventory. "
"The quantity of inventory remains unchanged, resulting in a "
"change in the price",
states={'draft': [('readonly', False)]})
remarks = fields.Text('Remarks',
help="Displays by default Inventory Revaluation. "
"This text is copied to the journal entry.",
readonly=True,
default='Inventory Revaluation',
states={'draft': [('readonly', False)]})
state = fields.Selection(selection=_STATES,
string='Status',
readonly=True,
required=True,
default='draft',
states={'draft': [('readonly', False)]})
company_id = fields.Many2one(
comodel_name='res.company', string='Company', readonly=True,
default=lambda self: self.env['res.company']._company_default_get(
'stock.inventory.revaluation'),
states={'draft': [('readonly', False)]})
document_date = fields.Date(
'Creation date', required=True, readonly=True,
default=lambda self: fields.Date.context_today(self),
states={'draft': [('readonly', False)]})
journal_id = fields.Many2one('account.journal', 'Journal',
default=_default_journal,
readonly=True,
states={'draft': [('readonly', False)]})
line_ids = fields.One2many('stock.inventory.revaluation.line',
'revaluation_id',
string='Revaluation lines',
readonly=False,
states={'posted': [('readonly', True)]})
@api.model
def create(self, values):
sequence_obj = self.env['ir.sequence']
if values.get('name', '/') == '/':
values['name'] = sequence_obj.get('stock.inventory.revaluation')
return super(StockInventoryRevaluation, self).create(values)
@api.one
def post(self):
for line in self.line_ids:
if line.product_template_id.valuation != 'real_time':
continue
line.post()
return True
@api.multi
def button_post(self):
self.post()
self.write({'state': 'posted'})
return True
@api.multi
def button_draft(self):
self.write({'state': 'draft'})
return True
@api.multi
def button_cancel(self):
moves = self.env['account.move']
for line in self.line_ids:
if line.move_id:
moves += line.move_id
for line_quant in line.line_quant_ids:
if line_quant.move_id:
moves += line_quant.move_id
line_quant.quant_id.write({'cost': line_quant.old_cost})
if moves:
# second, invalidate the move(s)
moves.button_cancel()
# delete the move this revaluation was pointing to
# Note that the corresponding move_lines and move_reconciles
# will be automatically deleted too
moves.unlink()
self.write({'state': 'cancel'})
return True
class StockInventoryRevaluationLine(models.Model):
_name = 'stock.inventory.revaluation.line'
_description = 'Inventory revaluation line'
@api.one
def _get_product_template_qty(self):
self.qty_available = 0
for prod_variant in self.product_template_id.product_variant_ids:
self.qty_available += prod_variant.qty_available
@api.one
def _calc_product_template_value(self):
qty_available = 0
current_value = 0.0
quant_obj = self.env['stock.quant']
for prod_variant in self.product_template_id.product_variant_ids:
qty_available += prod_variant.qty_available
if self.product_template_id.cost_method == 'real':
quants = quant_obj.search([('product_id', '=',
prod_variant.id),
('location_id.usage', '=',
'internal')])
for quant in quants:
current_value += quant.cost
else:
current_value = \
self.product_template_id.standard_price * qty_available
self.current_value = current_value
@api.one
@api.depends("product_template_id", "product_template_id.standard_price")
def _calc_current_cost(self):
self.current_cost = self.product_template_id.standard_price
revaluation_id = fields.Many2one('stock.inventory.revaluation',
'Stock Inventory Revaluation',
required=True,
ondelete='cascade')
state = fields.Selection(selection=_STATES,
string='UoM', readonly=True,
related="revaluation_id.state")
product_template_id = fields.Many2one('product.template', 'Product',
required=True,
domain=[('type', '=', 'product')])
cost_method = fields.Selection(string="Cost Method", readonly=True,
related='product_template_id.cost_method')
uom_id = fields.Many2one('product.uom', 'UoM', readonly=True,
related="product_template_id.uom_id")
old_cost = fields.Float('Old cost',
help='Displays the previous cost of the '
'product.',
digits=dp.get_precision('Product Price'),
readonly=True)
current_cost = fields.Float('Current cost',
help='Displays the current cost of the '
'product.',
digits=dp.get_precision('Product Price'),
compute="_calc_current_cost",
readonly=True)
new_cost = fields.Float('New cost',
help="Enter the new cost you wish to assign to "
"the product. Relevant only when the "
"selected revaluation type is Price Change.",
digits=dp.get_precision('Product Price'))
current_value = fields.Float('Current value',
help='Displays the current value of the '
'product.',
digits=dp.get_precision('Account'),
compute="_calc_product_template_value",
readonly=True)
old_value = fields.Float('Old value',
help='Displays the current value of the product.',
digits=dp.get_precision('Account'),
readonly=True)
new_value = fields.Float('Credit/Debit amount',
help="Enter the amount you wish to credit or "
"debit from the current inventory value of "
"the item. Enter credit as a negative value."
"Relevant only if the selected revaluation "
"type is Inventory Credit/Debit.",
digits=dp.get_precision('Account'))
qty_available = fields.Float(
'Quantity On Hand', compute='_get_product_template_qty',
digits_compute=dp.get_precision('Product Unit of Measure'))
increase_account_id = fields.Many2one(
'account.account', 'Increase Account',
help="Define the G/L accounts to be used as the balancing account in "
"the transaction created by the revaluation. The Increase "
"Account is used when the inventory value is increased due to "
"the revaluation.")
decrease_account_id = fields.Many2one(
'account.account', 'Decrease Account',
help="Define the G/L accounts to be used as the balancing account in "
"the transaction created by the revaluation. The Decrease "
"Account is used when the inventory value is decreased.")
company_id = fields.Many2one(
comodel_name='res.company', string='Company', readonly=True,
related="revaluation_id.company_id")
move_id = fields.Many2one('account.move', 'Account move', readonly=True)
revaluation_type = fields.Selection(
string="Revaluation Type", readonly=True,
related='revaluation_id.revaluation_type',
default='price_change')
line_quant_ids = fields.One2many('stock.inventory.revaluation.line.quant',
'line_id',
string='Revaluation line quants')
_sql_constraints = [
('inv_valu_line_prod_temp_uniq',
'unique (revaluation_id, product_template_id)',
_('Cannot enter the same product multiple times in the same '
'inventory valuation!'))]
@api.one
@api.constrains('product_template_id', 'company_id')
def _check_is_stockable(self):
if self.product_template_id.type != 'product':
raise UserError(_('Configuration error!\nThe product must be '
'stockable.'))
@api.one
@api.onchange("product_template_id")
def _onchange_product_template_id(self):
if self.product_template_id:
self.increase_account_id = self.product_template_id.categ_id and \
self.product_template_id.categ_id.\
property_inventory_revaluation_increase_account_categ
self.decrease_account_id = self.product_template_id.categ_id and \
self.product_template_id.categ_id.\
property_inventory_revaluation_decrease_account_categ
self.revaluation_type = self.revaluation_id.revaluation_type
@api.model
def _prepare_move_data(self, date_move):
period = self.env['account.period'].find(date_move)[0]
return {
'narration': self.revaluation_id.remarks,
'date': date_move,
'ref': self.revaluation_id.name,
'journal_id': self.revaluation_id.journal_id.id,
'period_id': period.id,
}
@api.model
def _prepare_debit_move_line_data(self, amount, account_id, prod_id):
return {
'name': self.revaluation_id.name,
'date': self.move_id.date,
'product_id': prod_id,
'account_id': account_id,
'move_id': self.move_id.id,
'debit': amount
}
@api.model
def _prepare_credit_move_line_data(self, amount, account_id, prod_id):
return {
'name': self.revaluation_id.name,
'date': self.move_id.date,
'product_id': prod_id,
'account_id': account_id,
'move_id': self.move_id.id,
'credit': amount
}
@api.model
def _create_accounting_entry(self, amount_diff):
timenow = time.strftime('%Y-%m-%d')
move_data = self._prepare_move_data(timenow)
datas = self.env['product.template'].get_product_accounts(
self.product_template_id.id)
self.move_id = self.env['account.move'].create(move_data).id
move_line_obj = self.env['account.move.line']
if not self.decrease_account_id or not self.increase_account_id:
raise UserError(_("Please add a Increase Account and "
"a Decrease Account."))
for prod_variant in self.product_template_id.product_variant_ids:
qty = prod_variant.qty_available
if qty:
if amount_diff > 0:
debit_account_id = self.decrease_account_id.id
credit_account_id = \
datas['property_stock_valuation_account_id']
else:
debit_account_id = \
datas['property_stock_valuation_account_id']
credit_account_id = self.increase_account_id.id
move_line_data = self._prepare_debit_move_line_data(
abs(amount_diff), debit_account_id, prod_variant.id)
move_line_obj.create(move_line_data)
move_line_data = self._prepare_credit_move_line_data(
abs(amount_diff), credit_account_id, prod_variant.id)
move_line_obj.create(move_line_data)
if self.move_id.journal_id.entry_posted:
self.move_id.post()
@api.one
def post(self):
amount_diff = 0.0
if self.product_template_id.cost_method == 'real':
for line_quant in self.line_quant_ids:
amount_diff += line_quant.get_total_value()
line_quant.write_new_cost()
if amount_diff == 0.0:
return True
else:
if self.product_template_id.cost_method in ['standard', 'average']:
if self.revaluation_id.revaluation_type == 'price_change':
diff = self.current_cost - self.new_cost
amount_diff = self.qty_available * diff
else:
amount_diff = self.current_value - self.new_value
if self.new_value < 0:
raise UserError(_("The new value for product %s "
"cannot be negative"
% self.product_template_id.name))
if self.qty_available <= 0.0:
raise UserError(
_("Cannot do an inventory value change if the "
"quantity available for product %s "
"is 0 or negative" %
self.product_template_id.name))
if self.revaluation_id.revaluation_type == 'price_change':
self.old_cost = self.current_cost
self.product_template_id.write({'standard_price':
self.new_cost})
else:
self.old_cost = self.current_cost
self.old_value = self.current_value
value_diff = self.current_value - self.new_value
new_cost = value_diff / self.qty_available
self.product_template_id.write({'standard_price':
new_cost})
if self.product_template_id.valuation == 'real_time':
self._create_accounting_entry(amount_diff)
class StockInventoryRevaluationLineQuant(models.Model):
_name = 'stock.inventory.revaluation.line.quant'
_description = 'Inventory revaluation line quant'
line_id = fields.Many2one('stock.inventory.revaluation.line',
'Revaluation Line', required=True,
readonly=True)
quant_id = fields.Many2one('stock.quant', 'Quant', required=True,
readonly=True,
domain=[('product_id.type', '=', 'product')])
product_id = fields.Many2one('product.product', 'Product',
readonly=True,
related="quant_id.product_id")
location_id = fields.Many2one('stock.location', 'Location',
readonly=True,
related="quant_id.location_id")
qty = fields.Float('Quantity', readonly=True,
related="quant_id.qty")
in_date = fields.Datetime('Incoming Date', readonly=True,
related="quant_id.in_date")
current_cost = fields.Float('Current Cost',
readonly=True,
related="quant_id.cost")
old_cost = fields.Float('Previous cost',
help='Shows the previous cost of the quant',
readonly=True)
new_cost = fields.Float('New Cost',
help="Enter the new cost you wish to assign to "
"the Quant. Relevant only when the "
"selected revaluation type is Price Change.",
digits=dp.get_precision('Product Price'),
copy=False)
def get_total_value(self):
amount_diff = 0.0
if self.product_id.product_tmpl_id.cost_method == 'real':
if self.line_id.revaluation_id.revaluation_type != 'price_change':
raise UserError(_("You can only post quant cost changes."))
else:
diff = self.current_cost - self.new_cost
amount_diff = self.qty * diff
return amount_diff
def write_new_cost(self):
self.old_cost = self.current_cost
self.quant_id.write({'cost': self.new_cost})
return True

View File

@@ -0,0 +1,4 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_stock_inventory_revaluation,stock.inventory.revaluation,model_stock_inventory_revaluation,stock_account.group_inventory_valuation,1,1,1,1
access_stock_inventory_revaluation_line,stock.inventory.revaluation.line,model_stock_inventory_revaluation_line,stock_account.group_inventory_valuation,1,1,1,1
access_stock_inventory_revaluation_line_quant,stock.inventory.revaluation.line.quant,model_stock_inventory_revaluation_line_quant,stock_account.group_inventory_valuation,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_stock_inventory_revaluation stock.inventory.revaluation model_stock_inventory_revaluation stock_account.group_inventory_valuation 1 1 1 1
3 access_stock_inventory_revaluation_line stock.inventory.revaluation.line model_stock_inventory_revaluation_line stock_account.group_inventory_valuation 1 1 1 1
4 access_stock_inventory_revaluation_line_quant stock.inventory.revaluation.line.quant model_stock_inventory_revaluation_line_quant stock_account.group_inventory_valuation 1 1 1 1

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="1">
<record model="ir.rule" id="stock_inventory_revaluation_comp_rule">
<field name="name">Stock Inventory Revaluation multi-company</field>
<field name="model_id" ref="model_stock_inventory_revaluation"/>
<field name="global" eval="True"/>
<field name="domain_force">['|',('company_id','=',False),
('company_id','child_of',[user.company_id.id])]</field>
</record>
<record model="ir.rule" id="stock_inventory_revaluation_line_comp_rule">
<field name="name">Stock Inventory Revaluation line multi-company</field>
<field name="model_id" ref="model_stock_inventory_revaluation_line"/>
<field name="global" eval="True"/>
<field name="domain_force">['|',('company_id','=',False),
('company_id','child_of',[user.company_id.id])]</field>
</record>
</data>
</openerp>

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
# © 2015 Eficent Business and IT Consulting Services S.L.
# - Jordi Ballester Alomar
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from . import test_stock_inventory_revaluation

View File

@@ -0,0 +1,240 @@
# -*- coding: utf-8 -*-
# © 2015 Eficent Business and IT Consulting Services S.L.
# - Jordi Ballester Alomar
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from openerp.tests.common import TransactionCase
from datetime import datetime
from datetime import date, timedelta
class TestStockInventoryRevaluation(TransactionCase):
"""Test that the Inventory is Revaluated when the
inventory price for any product is changed."""
def setUp(self):
super(TestStockInventoryRevaluation, self).setUp()
# Get required Model
self.product_model = self.env['product.product']
self.product_ctg_model = self.env['product.category']
self.reval_model = self.env['stock.inventory.revaluation']
self.reval_line_model = self.env['stock.inventory.revaluation.line']
self.account_model = self.env['account.account']
self.acc_type_model = self.env['account.account.type']
self.reval_line_quant_model = self.\
env['stock.inventory.revaluation.line.quant']
self.get_quant_model = self.\
env['stock.inventory.revaluation.line.get.quant']
self.stock_change_model = self.env['stock.change.product.qty']
self.stock_lot_model = self.env['stock.production.lot']
self.stock_location_model = self.env['stock.location']
# Get required Model data
self.fixed_account = self.env.ref('account.xfa')
self.purchased_stock = self.env.ref('account.stk')
self.debtors_account = self.env.ref('account.a_recv')
self.cash_account = self.env.ref('account.cash')
self.product_uom = self.env.ref('product.product_uom_unit')
self.journal = self.env.ref('account.miscellaneous_journal')
self.company = self.env.ref('base.main_company')
location = self.stock_location_model.search([('name', '=', 'WH')])
self.location = self.stock_location_model.search([('location_id', '=',
location.id)])
# Create account for Goods Received Not Invoiced
name = 'Goods Received Not Invoiced'
code = 'grni'
acc_type = 'equity'
self.account_grni = self._create_account(acc_type, name, code,
self.company)
# Create account for Cost of Goods Sold
name = 'Cost of Goods Sold'
code = 'cogs'
acc_type = 'expense'
self.account_cogs = self._create_account(acc_type, name, code,
self.company)
# Create account for Inventory
name = 'Inventory'
code = 'inventory'
acc_type = 'asset'
self.account_inventory = self._create_account(acc_type, name, code,
self.company)
# Create account for Inventory Revaluation
name = 'Inventory Revaluation'
code = 'revaluation'
acc_type = 'expense'
self.account_revaluation = self._create_account(acc_type, name, code,
self.company)
# Create product category
self.product_ctg = self._create_product_category()
# Create a Product with real cost
standard_price = 10.0
list_price = 20.0
self.product_real = self._create_product('real', standard_price,
list_price)
# Add default quantity
quantity = 20.00
self._update_product_qty(self.product_real, self.location, quantity)
# Create a Product with average cost
standard_price = 10.0
list_price = 20.0
self.product_average = self._create_product('average', standard_price,
list_price)
# Add default quantity
quantity = 20.00
self._update_product_qty(self.product_average, self.location, quantity)
def _create_account(self, acc_type, name, code, company):
"""Create an account."""
type_ids = self.acc_type_model.search([('code', '=', acc_type)])
account = self.account_model.create({
'name': name,
'code': code,
'type': 'other',
'user_type': type_ids.ids and type_ids.ids[0],
'company_id': company.id
})
return account
def _create_product_category(self):
product_ctg = self.product_ctg_model.create({
'name': 'test_product_ctg',
'property_stock_valuation_account_id': self.account_inventory.id,
'property_inventory_revaluation_increase_account_categ':
self.account_revaluation.id,
'property_inventory_revaluation_decrease_account_categ':
self.account_revaluation.id,
})
return product_ctg
def _create_product(self, cost_method, standard_price, list_price):
"""Create a Product with inventory valuation set to auto."""
product = self.product_model.create({
'name': 'test_product',
'categ_id': self.product_ctg.id,
'type': 'product',
'standard_price': standard_price,
'list_price': list_price,
'valuation': 'real_time',
'cost_method': cost_method,
'property_stock_account_input': self.account_grni.id,
'property_stock_account_output': self.account_cogs.id,
})
return product
def _create_inventory_revaluation(self, journal, revaluation_type):
"""Create a Inventory Revaluation with revaluation_type set to
price_change to recalculate inventory value according to new price."""
inventory = self.reval_model.create({
'name': 'test_inventory_revaluation',
'document_date': datetime.today(),
'revaluation_type': revaluation_type,
'journal_id': journal.id,
})
return inventory
def _create_inventory_revaluation_line(self, revaluation, product):
"""Create a Inventory Revaluation line by applying
increase and decrease account to it."""
self.increase_account_id = product.categ_id and \
product.categ_id.\
property_inventory_revaluation_increase_account_categ
self.decrease_account_id = product.categ_id and \
product.categ_id.\
property_inventory_revaluation_decrease_account_categ
line = self.reval_line_model.create({
'product_template_id': product.id,
'revaluation_id': revaluation.id,
'increase_account_id': self.increase_account_id.id,
'decrease_account_id': self.decrease_account_id.id,
})
return line
def _update_product_qty(self, product, location, quantity):
"""Update Product quantity."""
product_qty = self.stock_change_model.create({
'location_id': location.id,
'product_id': product.id,
'new_quantity': quantity,
})
product_qty.change_product_qty()
return product_qty
def _get_quant(self, date_from, line):
"""Get Quants for Inventory Revaluation between the date supplied."""
quant = self.get_quant_model.create({
'date_from': date_from,
'date_to': datetime.today(),
})
line_context = {
'active_id': line.id,
'active_ids': line.ids,
'active_model': 'stock.inventory.revaluation.line',
}
quant.with_context(line_context).process()
for line_quant in line.line_quant_ids:
line_quant.new_cost = 8.0
def test_inventory_revaluation_price_change(self):
"""Test that the inventory is revaluated when the
inventory price for any product is changed."""
# Create an Inventory Revaluation
revaluation_type = 'price_change'
invent_price_change = self._create_inventory_revaluation(
self.journal, revaluation_type)
# Create an Inventory Revaluation Line for real cost product
invent_line_real = \
self._create_inventory_revaluation_line(
invent_price_change, self.product_real.product_tmpl_id)
# Create an Inventory Revaluation Line Quant
date_from = date.today() - timedelta(1)
self._get_quant(date_from, invent_line_real)
# Create an Inventory Revaluation Line for average cost product
invent_line_avg = self._create_inventory_revaluation_line(
invent_price_change, self.product_average.product_tmpl_id)
# Post the inventory revaluation
invent_line_avg.new_cost = 8.00
invent_price_change.button_post()
expected_result = (10.00 - 8.00) * 20.00
for line in invent_price_change.line_ids:
for move_line in line.move_id.line_id:
if move_line.debit:
self.assertEqual(move_line.debit, expected_result,
'Incorrect inventory revaluation for '
'type Price Change.')
def test_inventory_revaluation_value_change(self):
"""Test that the inventory is revaluated when the
inventory price for any product is changed."""
# Create an Inventory Revaluation for value change
revaluation_type = 'inventory_value'
invent_inventory_value = self._create_inventory_revaluation(
self.journal, revaluation_type)
# Create an Inventory Revaluation Line for average cost product
invent_line_average = self._create_inventory_revaluation_line(
invent_inventory_value, self.product_average.product_tmpl_id)
invent_line_average.new_value = 100.00
# Post the inventory revaluation
invent_inventory_value.button_post()
for line in invent_inventory_value.line_ids:
for move_line in line.move_id.line_id:
if move_line.debit:
self.assertEqual(move_line.debit, 100.0,
'Incorrect inventory revaluation for '
'type Inventory Debit/Credit.')

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="view_category_property_form" model="ir.ui.view">
<field name="name">product.category.stock.property.form.inherit</field>
<field name="model">product.category</field>
<field name="inherit_id"
ref="stock_account.view_category_property_form"/>
<field name="arch" type="xml">
<group name="account_property" position="inside">
<field name="property_inventory_revaluation_increase_account_categ" domain="[('type','&lt;&gt;','view'),('type','&lt;&gt;','consolidation')]"/>
<field name="property_inventory_revaluation_decrease_account_categ" domain="[('type','&lt;&gt;','view'),('type','&lt;&gt;','consolidation')]"/>
</group>
</field>
</record>
</data>
</openerp>

View File

@@ -0,0 +1,282 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="view_stock_inventory_revaluation_form" model="ir.ui.view">
<field name="name">stock.inventory.revaluation.form</field>
<field name="model">stock.inventory.revaluation</field>
<field name="arch" type="xml">
<form string="Stock Inventory Revaluation">
<header>
<button name="button_draft"
states="cancel"
string="Back to Draft"
type="object"/>
<button name="button_post" states="draft"
string="Post" type="object"
class="oe_highlight"/>
<button name="button_cancel" states="draft,posted"
string="Cancel" type="object"
class="oe_highlight"/>
<field name="state" widget="statusbar"
statusbar_visible="draft,done,cancel"
statusbar_colors='{"done":"blue"}'/>
</header>
<sheet>
<div class="oe_edit_only">
<label for="name" class="oe_inline"/>
</div>
<h1>
<field name="name" class="oe_inline"/>
</h1>
<group name="main">
<group name="basic">
<field name="document_date"/>
<field name="revaluation_type"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
<group name="accounting">
<field name="journal_id"/>
</group>
<group name="remarks" colspan="2">
<field name="remarks"/>
</group>
</group>
<notebook>
<page string="Products">
<field name="line_ids" nolabel="1">
<tree string="Stock Inventory Revaluation Lines">
<field name="state" invisible="True"/>
<field name="revaluation_type"
invisible="1"/>
<field name="product_template_id"/>
<field name="uom_id"/>
<field name="cost_method"/>
<field name="current_cost"/>
<field name="old_cost"
attrs="{'invisible':['|', '|', ('revaluation_type', '!=', 'price_change'), ('cost_method','=', 'real'), ('state', '!=', 'posted')]}"/>
<field name="new_cost"
attrs="{'invisible':['|', ('revaluation_type', '!=', 'price_change'), ('cost_method','=', 'real')]}"/>
<field name="old_value"
attrs="{'invisible':['|', '|', ('revaluation_type', '!=', 'inventory_value'), ('cost_method','=', 'real'), ('state', '!=', 'posted')]}"/>
<field name="current_value"/>
<field name="new_value"
attrs="{'invisible':['|', ('revaluation_type', '!=', 'inventory_value'), ('cost_method','=', 'real')]}"/>
<field name="qty_available"/>
<field name="increase_account_id"/>
<field name="decrease_account_id"/>
<field name="move_id"/>
</tree>
<form string="Stock Inventory Revaluation Lines">
<group>
<field name="state" invisible="True"/>
<group name="product">
<field name="product_template_id"/>
<field name="uom_id"/>
<field name="cost_method"/>
<field name="qty_available"/>
<field name="revaluation_type" invisible="1"/>
</group>
<group name="unit_cost">
<field name="old_cost"
attrs="{'invisible':['|', '|', ('revaluation_type', '!=', 'price_change'), ('cost_method','=', 'real'), ('state', '!=', 'posted')]}"/>
<field name="current_cost"/>
<field name="new_cost"
attrs="{'invisible':['|', ('revaluation_type', '!=', 'price_change'), ('cost_method','=', 'real')]}"/>
</group>
<group name="value">
<field name="old_value"
attrs="{'invisible':['|', '|', ('revaluation_type', '!=', 'inventory_value'), ('cost_method','=', 'real'), ('state', '!=', 'posted')]}"/>
<field name="current_value"/>
<field name="new_value"
attrs="{'invisible':['|', ('revaluation_type', '!=', 'inventory_value'), ('cost_method','=', 'real')]}"/>
</group>
<group name="accounting">
<field name="increase_account_id"/>
<field name="decrease_account_id"/>
<field name="move_id"/>
</group>
</group>
<notebook>
<page name="quant" string="Quants" attrs="{'invisible':[('cost_method','!=', 'real')]}">
<button
name="%(action_stock_inventory_revaluation_line_get_quant)d" string="Get Quants" type="action" class="oe_highlight" />
<field name="line_quant_ids"
nolabel="1">
<tree name="quants"
string="Quants"
create="false"
delete="false" editable="bottom">
<field name="quant_id"/>
<field name="in_date"/>
<field name="product_id"/>
<field name="location_id"/>
<field name="qty"/>
<field name="current_cost"/>
<field name="new_cost"/>
</tree>
</field>
</page>
</notebook>
</form>
</field>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="view_stock_inventory_revaluation_line_tree"
model="ir.ui.view">
<field name="name">stock.inventory.revaluation.line.tree</field>
<field name="model">stock.inventory.revaluation.line</field>
<field name="arch" type="xml">
<tree string="Stock Inventory Revaluation Lines">
<field
name="revaluation_type"
invisible="1"/>
<field name="product_template_id"/>
<field name="uom_id"/>
<field name="cost_method"/>
<field name="current_cost"/>
<field name="new_cost"
attrs="{'invisible':['|', ('revaluation_type', '!=', 'price_change'), ('cost_method','=', 'real_price')]}"/>
<field name="current_value"/>
<field name="new_value"
attrs="{'invisible':['|', ('revaluation_type', '!=', 'inventory_value'), ('cost_method','=', 'real_price')]}"/>
<field name="qty_available"/>
<field name="increase_account_id"/>
<field name="decrease_account_id"/>
<field name="move_id"/>
</tree>
</field>
</record>
<record id="view_stock_inventory_revaluation_line_form"
model="ir.ui.view">
<field name="name">stock.inventory.revaluation.line.form</field>
<field name="model">stock.inventory.revaluation.line</field>
<field name="arch" type="xml">
<form string="Stock Inventory Revaluation Lines">
<group>
<field name="state" invisible="True"/>
<group name="product">
<field name="product_template_id"/>
<field name="uom_id"/>
<field name="cost_method"/>
<field name="qty_available"/>
</group>
<group name="unit_cost">
<field name="old_cost"
attrs="{'invisible':['|', '|', ('revaluation_type', '!=', 'price_change'), ('cost_method','=', 'real'), ('state', '!=', 'posted')]}"/>
<field name="current_cost"/>
<field name="new_cost"
attrs="{'invisible':['|', ('revaluation_type', '!=', 'price_change'), ('cost_method','=', 'real')]}"/>
</group>
<group name="value">
<field name="old_value"
attrs="{'invisible':['|', '|', ('revaluation_type', '!=', 'inventory_value'), ('cost_method','=', 'real'), ('state', '!=', 'posted')]}"/>
<field name="current_value"/>
<field name="new_value"
attrs="{'invisible':['|', ('revaluation_type', '!=', 'inventory_value'), ('cost_method','=', 'real')]}"/>
</group>
<group name="accounting">
<field name="increase_account_id"/>
<field name="decrease_account_id"/>
<field name="move_id"/>
</group>
</group>
<notebook>
<page name="quant" string="Quants" attrs="{'invisible':[('cost_method','!=', 'real_price')]}">
<field name="line_quant_ids"
create="false" delete="false" nolabel="1"
editable="bottom"/>
</page>
</notebook>
</form>
</field>
</record>
<record id="view_stock_inventory_revaluation_line_quant_tree"
model="ir.ui.view">
<field name="name">stock.inventory.revaluation.line.quant.tree</field>
<field name="model">stock.inventory.revaluation.line.quant</field>
<field name="arch" type="xml">
<tree string="Stock Inventory Revaluation Line Quants">
<field name="quant_id"/>
<field name="in_date"/>
<field name="product_id"/>
<field name="location_id"/>
<field name="qty"/>
<field name="current_cost"/>
<field name="new_cost"/>
</tree>
</field>
</record>
<record id="view_stock_inventory_revaluation_line_quant_form"
model="ir.ui.view">
<field name="name">stock.inventory.revaluation.line.quant.form</field>
<field name="model">stock.inventory.revaluation.line.quant</field>
<field name="arch" type="xml">
<form string="Stock Inventory Revaluation Line Quants">
<field name="quant_id"/>
<field name="product_id"/>
<field name="location_id"/>
<field name="qty"/>
<field name="current_cost"/>
<field name="new_cost"/>
</form>
</field>
</record>
<record id="view_stock_inventory_revaluation_tree" model="ir.ui.view">
<field name="name">stock.inventory.revaluation.tree</field>
<field name="model">stock.inventory.revaluation</field>
<field name="arch" type="xml">
<tree string="Stock Inventory Revaluation">
<field name="name"/>
<field name="revaluation_type"/>
<field name="document_date"/>
<field name="state"/>
<field name="company_id" groups="base.group_multi_company"/>
</tree>
</field>
</record>
<record id="view_stock_inventory_revaluation_search" model="ir.ui.view">
<field name="name">stock.inventory.revaluation.search</field>
<field name="model">stock.inventory.revaluation</field>
<field name="arch" type="xml">
<search string="Search Stock Inventory Revaluation">
<field name="name"/>
<field name="revaluation_type"/>
<field name="document_date"/>
<field name="state"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="line_ids"/>
</search>
</field>
</record>
<record id="action_stock_inventory_revaluation_tree" model="ir.actions.act_window">
<field name="name">Inventory Revaluation</field>
<field name="res_model">stock.inventory.revaluation</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to start a new Stock Inventory Revaluation.
</p>
</field>
</record>
<menuitem action="action_stock_inventory_revaluation_tree"
id="menu_action_stock_inventory_revaluation_tree"
parent="stock.menu_stock_inventory_control"
groups="stock_account.group_inventory_valuation"/>
</data>
</openerp>

View File

@@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
# © 2015 Eficent Business and IT Consulting Services S.L.
# - Jordi Ballester Alomar
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from . import stock_inventory_revaluation_get_quants

View File

@@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
# © 2015 Eficent Business and IT Consulting Services S.L.
# - Jordi Ballester Alomar
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from openerp import api, fields, models
class StockInventoryRevaluationGetQuants(models.TransientModel):
_name = 'stock.inventory.revaluation.line.get.quant'
_description = 'Inventory revaluation line get Quants'
date_from = fields.Date('Date From')
date_to = fields.Date('Date To')
def _get_quant_search_criteria(self, product_variant):
domain = [('product_id', '=', product_variant.id),
('location_id.usage', '=', 'internal')]
if self.date_from:
domain.extend([('in_date', '>=', self.date_from)])
if self.date_to:
domain.extend([('in_date', '<=', self.date_to)])
return domain
def _select_quants(self, line):
quant_l = []
quant_obj = self.env['stock.quant']
for prod_variant in line.product_template_id.product_variant_ids:
search_domain = self._get_quant_search_criteria(prod_variant)
quants = quant_obj.search(search_domain)
for quant in quants:
quant_l.append(quant)
return quant_l
def _prepare_line_quant_data(self, line, quant):
return {
'line_id': line.id,
'quant_id': quant.id,
'new_cost': quant.cost
}
@api.one
def process(self):
if self.env.context.get('active_id', False):
line_obj = self.env['stock.inventory.revaluation.line']
line_quant_obj = self.env['stock.inventory.revaluation.line.quant']
line = line_obj.browse(self.env.context['active_id'])
# Delete the previous records
for line_quant in line.line_quant_ids:
line_quant.unlink()
quants = self._select_quants(line)
for q in quants:
q_data = self._prepare_line_quant_data(line, q)
line_quant_obj.create(q_data)
return {'type': 'ir.actions.act_window_close'}

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record model="ir.ui.view"
id="stock_inventory_revaluation_line_get_quant_form">
<field name="name">stock.inventory.revaluation.line.get.quant.form</field>
<field name="model">stock.inventory.revaluation.line.get.quant</field>
<field name="arch" type="xml">
<form string="Get Quants">
<group colspan="2" col="2">
<field name="date_from"/>
<field name="date_to"/>
</group>
<footer>
<button name="process" string="Get Quants" type="object"
class="oe_highlight"/>
or
<button string="Cancel" class="oe_link" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="action_stock_inventory_revaluation_line_get_quant"
model="ir.actions.act_window">
<field name="name">Get Quants for Inventory Revaluation</field>
<field name="res_model">stock.inventory.revaluation.line.get.quant</field>
<field name="type">ir.actions.act_window</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="view_id"
ref="stock_inventory_revaluation_line_get_quant_form"/>
<field name="target">new</field>
</record>
</data>
</openerp>