odoo/addons/stock_account/product.py

307 lines
16 KiB
Python

# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.osv import fields, osv
from openerp.tools.translate import _
import time
class prices_history(osv.osv):
"""
Keep track of the ``product.product`` standard prices as they are changed.
"""
_name = 'prices.history'
_rec_name = 'datetime'
_order = 'datetime desc'
_columns = {
'company_id': fields.many2one('res.company', required=True),
'product_id': fields.many2one('product.product', 'Product', required=True),
'datetime': fields.datetime('Historization Time'),
'cost': fields.float('Historized Cost'),
'reason': fields.char('Reason'),
# TODO 'origin': openerp.osv.fields.reference(),
#'quant_id': openerp.osv.fields.many2one('stock.quant'),
}
def _get_default_company(self, cr, uid, context=None):
if 'force_company' in context:
return context['force_company']
else:
company = self.pool['res.users'].browse(cr, uid, uid,
context=context).company_id
return company.id if company else False
_defaults = {
#'quant_id': False,
'datetime': fields.datetime.now,
'company_id': _get_default_company,
}
class product_product(osv.osv):
_inherit = "product.product"
def get_history_price(self, cr, uid, product_id, company_id, context=None):
if context is None:
context = {}
date = context.get('history_date', time.strftime('%Y-%m-%d %H:%M:%s'))
prices_history_obj = self.pool.get('prices.history')
history_ids = prices_history_obj.search(cr, uid, [('company_id', '=', company_id), ('product_id', '=', product_id), ('datetime', '<=', date)], limit=1)
if history_ids:
return prices_history_obj.read(cr, uid, history_ids[0], ['cost'], context=context)['cost']
raise osv.except_osv(_('Error!'), _("No standard price associated for product %d for the given date" % (product_id)))
def get_product_accounts(self, cr, uid, product_id, context=None):
""" To get the stock input account, stock output account and stock journal related to product.
@param product_id: product id
@return: dictionary which contains information regarding stock input account, stock output account and stock journal
"""
if context is None:
context = {}
product_obj = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
stock_input_acc = product_obj.property_stock_account_input and product_obj.property_stock_account_input.id or False
if not stock_input_acc:
stock_input_acc = product_obj.categ_id.property_stock_account_input_categ and product_obj.categ_id.property_stock_account_input_categ.id or False
stock_output_acc = product_obj.property_stock_account_output and product_obj.property_stock_account_output.id or False
if not stock_output_acc:
stock_output_acc = product_obj.categ_id.property_stock_account_output_categ and product_obj.categ_id.property_stock_account_output_categ.id or False
journal_id = product_obj.categ_id.property_stock_journal and product_obj.categ_id.property_stock_journal.id or False
account_valuation = product_obj.categ_id.property_stock_valuation_account_id and product_obj.categ_id.property_stock_valuation_account_id.id or False
return {
'stock_account_input': stock_input_acc,
'stock_account_output': stock_output_acc,
'stock_journal': journal_id,
'property_stock_valuation_account_id': account_valuation
}
# FP Note:;too complex, not good, should be implemented at quant level TODO
def do_change_standard_price(self, cr, uid, ids, datas, context=None):
""" Changes the Standard Price of Product and creates an account move accordingly.
@param datas : dict. contain default datas like new_price, stock_output_account, stock_input_account, stock_journal
@param context: A standard dictionary
@return:
"""
location_obj = self.pool.get('stock.location')
move_obj = self.pool.get('account.move')
move_line_obj = self.pool.get('account.move.line')
if context is None:
context = {}
new_price = datas.get('new_price', 0.0)
stock_output_acc = datas.get('stock_output_account', False)
stock_input_acc = datas.get('stock_input_account', False)
journal_id = datas.get('stock_journal', False)
product_obj=self.browse(cr, uid, ids, context=context)[0]
account_valuation = product_obj.categ_id.property_stock_valuation_account_id
account_valuation_id = account_valuation and account_valuation.id or False
if not account_valuation_id: raise osv.except_osv(_('Error!'), _('Specify valuation Account for Product Category: %s.') % (product_obj.categ_id.name))
move_ids = []
loc_ids = location_obj.search(cr, uid,[('usage','=','internal')])
for rec_id in ids:
for location in location_obj.browse(cr, uid, loc_ids, context=context):
c = context.copy()
c.update({
'location': location.id,
'compute_child': False
})
product = self.browse(cr, uid, rec_id, context=c)
qty = product.qty_available
diff = product.standard_price - new_price
if not diff: raise osv.except_osv(_('Error!'), _("No difference between standard price and new price!"))
if qty:
company_id = location.company_id and location.company_id.id or False
if not company_id: raise osv.except_osv(_('Error!'), _('Please specify company in Location.'))
#
# Accounting Entries
#
if not journal_id:
journal_id = product.categ_id.property_stock_journal and product.categ_id.property_stock_journal.id or False
if not journal_id:
raise osv.except_osv(_('Error!'),
_('Please define journal '\
'on the product category: "%s" (id: %d).') % \
(product.categ_id.name,
product.categ_id.id,))
move_id = move_obj.create(cr, uid, {
'journal_id': journal_id,
'company_id': company_id
})
move_ids.append(move_id)
if diff > 0:
if not stock_input_acc:
stock_input_acc = product.\
property_stock_account_input.id
if not stock_input_acc:
stock_input_acc = product.categ_id.\
property_stock_account_input_categ.id
if not stock_input_acc:
raise osv.except_osv(_('Error!'),
_('Please define stock input account ' \
'for this product: "%s" (id: %d).') % \
(product.name,
product.id,))
amount_diff = qty * diff
move_line_obj.create(cr, uid, {
'name': product.name,
'account_id': stock_input_acc,
'debit': amount_diff,
'move_id': move_id,
})
move_line_obj.create(cr, uid, {
'name': product.categ_id.name,
'account_id': account_valuation_id,
'credit': amount_diff,
'move_id': move_id
})
elif diff < 0:
if not stock_output_acc:
stock_output_acc = product.\
property_stock_account_output.id
if not stock_output_acc:
stock_output_acc = product.categ_id.\
property_stock_account_output_categ.id
if not stock_output_acc:
raise osv.except_osv(_('Error!'),
_('Please define stock output account ' \
'for this product: "%s" (id: %d).') % \
(product.name,
product.id,))
amount_diff = qty * -diff
move_line_obj.create(cr, uid, {
'name': product.name,
'account_id': stock_output_acc,
'credit': amount_diff,
'move_id': move_id
})
move_line_obj.create(cr, uid, {
'name': product.categ_id.name,
'account_id': account_valuation_id,
'debit': amount_diff,
'move_id': move_id
})
self.write(cr, uid, rec_id, {'standard_price': new_price})
return move_ids
def create(self, cr, uid, vals, context=None):
product_id = super(product_product, self).create(cr, uid, vals, context=context)
price_history_obj = self.pool['prices.history']
if vals.get('cost_method') in ('standard', 'average'):
price_history_obj.create(cr, uid, {
'product_id': product_id,
'cost': vals.get('standard_price', 0.0),
'reason': _('Product created and standard price set'),
}, context=context)
return product_id
def write(self, cr, uid, ids, values, context=None):
if 'standard_price' in values:
price_history_obj = self.pool['prices.history']
for product in self.browse(cr, uid, ids, context=context):
if product.cost_method in ('standard', 'average'):
price_history_obj.create(cr, uid, {
'product_id': product.id,
'cost': values['standard_price'],
'reason': 'standard_price is changed.',
# Provided by self._defaults:
# 'company_id':
# 'quant_id':
# 'datetime':
}, context=context)
elif product.cost_method in ('real',):
# For a 'real' cost_method, entries in standard_prices are
# created when quants are created. #TODO not true
pass
return super(product_product, self).write(cr, uid, ids, values, context=context)
_columns = {
'valuation':fields.property(type='selection', selection=[('manual_periodic', 'Periodical (manual)'),
('real_time','Real Time (automated)'),], string = 'Inventory Valuation',
help="If real-time valuation is enabled for a product, the system will automatically write journal entries corresponding to stock moves, with product price as specified by the 'Costing Method'" \
"The inventory variation account set on the product category will represent the current inventory value, and the stock input and stock output account will hold the counterpart moves for incoming and outgoing products."
, required=True),
}
class product_template(osv.osv):
_name = 'product.template'
_inherit = 'product.template'
_columns = {
'cost_method': fields.property(type='selection', selection=[('standard', 'Standard Price'), ('average', 'Average Price'), ('real', 'Real Price')],
help="""Standard Price: The cost price is manually updated at the end of a specific period (usually every year).
Average Price: The cost price is recomputed at each incoming shipment and used for the product valuation.
Real Price: The cost price displayed is the price of the last outgoing product (will be use in case of inventory loss for example).""",
string="Costing Method", required=True),
'property_stock_account_input': fields.property(
type='many2one',
relation='account.account',
string='Stock Input Account',
help="When doing real-time inventory valuation, counterpart journal items for all incoming stock moves will be posted in this account, unless "
"there is a specific valuation account set on the source location. When not set on the product, the one from the product category is used."),
'property_stock_account_output': fields.property(
type='many2one',
relation='account.account',
string='Stock Output Account',
help="When doing real-time inventory valuation, counterpart journal items for all outgoing stock moves will be posted in this account, unless "
"there is a specific valuation account set on the destination location. When not set on the product, the one from the product category is used."),
}
class product_category(osv.osv):
_inherit = 'product.category'
_columns = {
'property_stock_journal': fields.property(
relation='account.journal',
type='many2one',
string='Stock Journal',
help="When doing real-time inventory valuation, this is the Accounting Journal in which entries will be automatically posted when stock moves are processed."),
'property_stock_account_input_categ': fields.property(
type='many2one',
relation='account.account',
string='Stock Input Account',
help="When doing real-time inventory valuation, counterpart journal items for all incoming stock moves will be posted in this account, unless "
"there is a specific valuation account set on the source location. This is the default value for all products in this category. It "
"can also directly be set on each product"),
'property_stock_account_output_categ': fields.property(
type='many2one',
relation='account.account',
string='Stock Output Account',
help="When doing real-time inventory valuation, counterpart journal items for all outgoing stock moves will be posted in this account, unless "
"there is a specific valuation account set on the destination location. This is the default value for all products in this category. It "
"can also directly be set on each product"),
'property_stock_valuation_account_id': fields.property(
type='many2one',
relation='account.account',
string="Stock Valuation Account",
help="When real-time inventory valuation is enabled on a product, this account will hold the current value of the products.",),
}