[WIP] cost_method moved to stock_account + stock_account implementation of stock valuation

bzr revid: qdp-launchpad@openerp.com-20130712141407-amspq13axc7x58l1
This commit is contained in:
Quentin (OpenERP) 2013-07-12 16:14:07 +02:00
parent c77e66bdc3
commit 63111b6510
17 changed files with 247 additions and 261 deletions

View File

@ -313,12 +313,6 @@ class product_template(osv.osv):
'volume': fields.float('Volume', help="The volume in m3."),
'weight': fields.float('Gross Weight', digits_compute=dp.get_precision('Stock Weight'), help="The gross weight in Kg."),
'weight_net': fields.float('Net Weight', digits_compute=dp.get_precision('Stock Weight'), help="The net weight in Kg."),
'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
FIFO Price: The cost price is recomputed at each outgoing shipment FIFO
LIFO Price: The cost price is recomputed at each outgoing shipment LIFO""",
string="Costing Method", required=True),
'warranty': fields.float('Warranty'),
'sale_ok': fields.boolean('Can be Sold', help="Specify if the product can be selected in a sales order line."),
'state': fields.selection([('',''),
@ -373,14 +367,13 @@ class product_template(osv.osv):
_defaults = {
'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'product.template', context=c),
'list_price': 1,
'cost_method': 'standard',
'standard_price': 0.0,
'sale_ok': 1,
'produce_delay': 1,
'uom_id': _get_uom_id,
'uom_po_id': _get_uom_id,
'uos_coeff' : 1.0,
'mes_type' : 'fixed',
'uos_coeff': 1.0,
'mes_type': 'fixed',
'categ_id' : _default_category,
'type' : 'consu',
}

View File

@ -96,8 +96,7 @@
<page string="Procurements" groups="base.group_user">
<group name="procurement">
<group name="general">
<field name="cost_method" groups="product.group_costing_method"/>
<field name="standard_price" attrs="{'readonly':[('cost_method','in', ['average', 'standard'])]}"/>
<field name="standard_price"/>
</group>
<group name="procurement_uom" groups="product.group_uom" string="Purchase">
<field name="uom_po_id"/>
@ -710,8 +709,7 @@
<group string="Base Prices">
<field name="list_price"/>
<field name="standard_price" attrs="{'readonly':[('cost_method','=','average')]}"/>
<field name="cost_method"/>
<field name="standard_price"/>
</group>
<group string="Weights">

View File

@ -22,7 +22,7 @@
"name" : "Product extension to track sales and purchases",
"version" : "1.0",
"author" : "OpenERP S.A.",
"depends" : ["product", "purchase", "sale", "mrp"],
"depends" : ["product", "purchase", "sale", "mrp", "stock_account"],
"category" : "Generic Modules/Inventory Control",
"description": """
Product extension. This module adds:

View File

@ -13,7 +13,6 @@
-
!record {model: product.product, id: product_product_hrmanger0}:
categ_id: product.product_category_6
cost_method: standard
mes_type: fixed
name: HR Manger
procure_method: make_to_stock

View File

@ -38,15 +38,15 @@ OpenERPs replenishment management rules enable the system to generate draft p
Dashboard / Reports for Purchase Management will include:
---------------------------------------------------------
* Request for Quotations
* Purchase Orders Waiting Approval
* Purchase Orders Waiting Approval
* Monthly Purchases by Category
* Receptions Analysis
* Purchase Analysis
""",
'author': 'OpenERP SA',
'website': 'http://www.openerp.com',
'images' : ['images/purchase_order.jpeg', 'images/purchase_analysis.jpeg', 'images/request_for_quotation.jpeg'],
'depends': ['stock', 'process'],
'images': ['images/purchase_order.jpeg', 'images/purchase_analysis.jpeg', 'images/request_for_quotation.jpeg'],
'depends': ['stock_account', 'process'],
'data': [
'security/purchase_security.xml',
'security/ir.model.access.csv',
@ -67,8 +67,8 @@ Dashboard / Reports for Purchase Management will include:
'res_config_view.xml',
],
'test': [
'test/fifo_price.yml',
'test/fifo_returns.yml',
'test/fifo_price.yml',
'test/fifo_returns.yml',
#'test/costmethodchange.yml',
'test/process/cancel_order.yml',
'test/process/rfq2order2done.yml',

View File

@ -15,7 +15,6 @@
-
!record {model: product.template, id: product_template_slidermobile0}:
categ_id: product_category_allproductssellable0
cost_method: standard
list_price: 200.0
mes_type: fixed
name: Slider Mobile
@ -30,7 +29,6 @@
-
!record {model: product.product, id: product_product_slidermobile0}:
categ_id: product_category_allproductssellable0
cost_method: standard
list_price: 200.0
mes_type: fixed
name: Slider Mobile

View File

@ -46,13 +46,22 @@
<field name="model">product.product</field>
<field name="inherit_id" ref="product.product_normal_form_view"/>
<field name="arch" type="xml">
<xpath expr="//group[@name='general']" position="after" >
<group name="procurement_help" class="oe_grey" col="1" groups="base.group_user">
<!--TODO: Should write explanation for different routes-->
</group>
</xpath>
<xpath expr="//div[@name='buttons']" position="inside">
<button string="Inventory" name="%(action_product_location_tree)d" type="action" attrs="{'invisible':[('type', '=', 'service')]}" groups="stock.group_locations"/>
<button string="Moves" name= "%(act_product_stock_move_open)d" type="action" attrs="{'invisible':[('type', '=', 'service')]}" groups="stock.group_stock_user"/>
<button string="Orderpoints" name="%(product_open_orderpoint)d" type="action" attrs="{'invisible':[('type', '=', 'service')]}"/>
</xpath>
<xpath expr="//field[@name='standard_price']" position="before">
<field name="supply_method" groups="base.group_user"/>
</xpath>
<group name="procurement_uom" position="before">
<group name="delay" string="Delays">
<label for="produce_delay" attrs="{'invisible':[('type','=','service')]}"/>
@ -81,6 +90,7 @@
<label for="incoming_qty"/>
<div>
<field name="incoming_qty" class="oe_inline"/>
<button string="⇒ Request Procurement" name="%(act_make_procurement)d" type="action" class="oe_link"/>
</div>
<field name="outgoing_qty" class="oe_inline"/>
<field name="virtual_available" class="oe_inline"/>
@ -105,7 +115,7 @@
<field name="property_stock_inventory" attrs="{'readonly':[('type','=','service')]}" domain="[('usage','=','inventory')]"/>
</group>
</group>
</field>
</record>
@ -173,28 +183,5 @@
</field>
</record>
<record model="ir.ui.view" id="product_form_view_procurement_button">
<field name="name">product.product.procurement</field>
<field name="model">product.product</field>
<field name="inherit_id" ref="stock.view_normal_procurement_locations_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='incoming_qty']" position="after">
<button string="⇒ Request Procurement" name="%(act_make_procurement)d" type="action" class="oe_link"/>
</xpath>
<xpath expr="//div[@name='buttons']" position="inside">
<button string="Orderpoints" name="%(product_open_orderpoint)d" type="action" attrs="{'invisible':[('type', '=', 'service')]}"/>
</xpath>
<xpath expr="//field[@name='cost_method']" position="before">
<field name="supply_method" groups="base.group_user"/>
</xpath>
<xpath expr="//group[@name='general']" position="after" >
<group name="procurement_help" class="oe_grey" col="1" groups="base.group_user">
<!--TODO: Should write explanation for different routes-->
</group>
</xpath>
</field>
</record>
</data>
</openerp>

View File

@ -63,11 +63,12 @@ class report_stock_inventory(osv.osv):
ctx = context.copy()
ctx['force_company'] = line.company_id.id
prod = product_obj.browse(cr, uid, line.product_id.id, context=ctx)
if prodbrow[(line.company_id.id, line.product_id.id)].cost_method in ('real'):
res[line.id] = line.value
else:
res[line.id] = prodbrow[(line.company_id.id, line.product_id.id)].standard_price * line.product_qty
res[line.id] = self._get_inventory_value(cr, uid, line, prodbrow, context=ctx)
return res
def _get_inventory_value(self, cr, uid, line, prodbrow, context=None):
return prodbrow[(line.company_id.id, line.product_id.id)].standard_price * line.product_qty
_columns = {
'product_id':fields.many2one('product.product', 'Product', readonly=True),
'product_categ_id':fields.many2one('product.category', 'Product Category', readonly=True),
@ -102,3 +103,4 @@ class report_stock_inventory(osv.osv):
);
""")
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1759,103 +1759,103 @@ class stock_move(osv.osv):
self.action_done(cr, uid, res, context=context)
return res
def price_calculation(self, cr, uid, ids, quants, context=None):
'''
This method puts the right price on the stock move,
adapts the price on the product when necessary
and creates the necessary stock move matchings
:param quants: are quants to be reconciled and needs to be done when IN move reconciles out move
It returns a list of tuples with (move_id, match_id)
which is used for generating the accounting entries when FIFO/LIFO
'''
product_obj = self.pool.get('product.product')
currency_obj = self.pool.get('res.currency')
matching_obj = self.pool.get('stock.move.matching')
uom_obj = self.pool.get('product.uom')
quant_obj = self.pool.get('stock.quant')
product_avail = {}
res = {}
for move in self.browse(cr, uid, ids, context=context):
# Initialize variables
res[move.id] = []
move_qty = move.product_qty
move_uom = move.product_uom.id
company_id = move.company_id.id
ctx = context.copy()
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
ctx['force_company'] = move.company_id.id
product = product_obj.browse(cr, uid, move.product_id.id, context=ctx)
cost_method = product.cost_method
product_uom_qty = uom_obj._compute_qty(cr, uid, move_uom, move_qty, product.uom_id.id, round=False)
if not product.id in product_avail:
product_avail[product.id] = product.qty_available
# Check if out -> do stock move matchings and if fifo/lifo -> update price
# only update the cost price on the product form on stock moves of type == 'out' because if a valuation has to be made without PO,
# for inventories for example we want to use the last value used for an outgoing move
if move.location_id.usage == 'internal' and move.location_dest_id.usage != 'internal':
fifo = (cost_method != 'lifo')
#Ok -> do calculation based on quants
price_amount = 0.0
amount = 0.0
#if move.id in quants???
#search quants_move which are the quants associated with this move, which are not propagated quants
quants_move = quant_obj.search(cr, uid, [('history_ids', 'in', move.id), ('propagated_from_id', '=', False)], context=context)
for quant in quant_obj.browse(cr, uid, quants_move, context=context):
price_amount += quant.qty * quant.price_unit
amount += quant.qty
# tuples = product_obj.get_stock_matchings_fifolifo(cr, uid, [product.id], move_qty, fifo,
# move_uom, move.company_id.currency_id.id, context=ctx) #TODO Would be better to use price_currency_id for migration?
# price_amount = 0.0
# amount = 0.0
# #Write stock matchings
# for match in tuples:
# matchvals = {'move_in_id': match[0], 'qty': match[1],
# 'move_out_id': move.id}
# match_id = matching_obj.create(cr, uid, matchvals, context=context)
# res[move.id].append(match_id)
# price_amount += match[1] * match[2]
# amount += match[1]
#Write price on out move
if product_avail[product.id] >= product_uom_qty and product.cost_method in ['real']:
if amount > 0:
self.write(cr, uid, move.id, {'price_unit': price_amount / move_qty}, context=context) #Should be converted
product_obj.write(cr, uid, product.id, {'standard_price': price_amount / amount}, context=ctx)
else:
pass
# raise osv.except_osv(_('Error'), _("Something went wrong finding quants ") + str(self.search(cr, uid, [('company_id','=', company_id), ('qty_remaining', '>', 0), ('state', '=', 'done'),
# ('location_id.usage', '!=', 'internal'), ('location_dest_id.usage', '=', 'internal'), ('product_id', '=', product.id)],
# order = 'date, id', context=context)) + str(move_qty) + str(move_uom) + str(move.company_id.currency_id.id))
else:
new_price = uom_obj._compute_price(cr, uid, product.uom_id.id, product.standard_price, move_uom)
self.write(cr, uid, move.id, {'price_unit': new_price}, context=ctx)
#Adjust product_avail when not average and move returned from
if product.cost_method != 'average':
product_avail[product.id] -= product_uom_qty
#Check if in => if price 0.0, take standard price / Update price when average price and price on move != standard price
if move.location_id.usage != 'internal' and move.location_dest_id.usage == 'internal':
if move.price_unit == 0.0:
new_price = uom_obj._compute_price(cr, uid, product.uom_id.id, product.standard_price, move_uom)
self.write(cr, uid, move.id, {'price_unit': new_price}, context=ctx)
elif product.cost_method == 'average':
move_product_price = uom_obj._compute_price(cr, uid, move_uom, move.price_unit, product.uom_id.id)
if product_avail[product.id] > 0.0:
amount_unit = product.standard_price
new_std_price = ((amount_unit * product_avail[product.id])\
+ (move_product_price * product_uom_qty))/(product_avail[product.id] + product_uom_qty)
else:
new_std_price = move_product_price
product_obj.write(cr, uid, [product.id], {'standard_price': new_std_price}, context=ctx)
# Should create the stock move matchings for previous outs for the negative stock that can be matched with is in
if product_avail[product.id] < 0.0: #TODO LATER
resneg = self._generate_negative_stock_matchings(cr, uid, [move.id], product, quants[move.id], context=ctx)
res[move.id] += resneg
product_avail[product.id] += product_uom_qty
return res
# def price_calculation(self, cr, uid, ids, quants, context=None):
# '''
# This method puts the right price on the stock move,
# adapts the price on the product when necessary
# and creates the necessary stock move matchings
# :param quants: are quants to be reconciled and needs to be done when IN move reconciles out move
#
# It returns a list of tuples with (move_id, match_id)
# which is used for generating the accounting entries when FIFO/LIFO
# '''
# product_obj = self.pool.get('product.product')
# currency_obj = self.pool.get('res.currency')
# matching_obj = self.pool.get('stock.move.matching')
# uom_obj = self.pool.get('product.uom')
# quant_obj = self.pool.get('stock.quant')
#
# product_avail = {}
# res = {}
# for move in self.browse(cr, uid, ids, context=context):
# # Initialize variables
# res[move.id] = []
# move_qty = move.product_qty
# move_uom = move.product_uom.id
# company_id = move.company_id.id
# ctx = context.copy()
# user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
# ctx['force_company'] = move.company_id.id
# product = product_obj.browse(cr, uid, move.product_id.id, context=ctx)
# cost_method = product.cost_method
# product_uom_qty = uom_obj._compute_qty(cr, uid, move_uom, move_qty, product.uom_id.id, round=False)
# if not product.id in product_avail:
# product_avail[product.id] = product.qty_available
#
# # Check if out -> do stock move matchings and if fifo/lifo -> update price
# # only update the cost price on the product form on stock moves of type == 'out' because if a valuation has to be made without PO,
# # for inventories for example we want to use the last value used for an outgoing move
# if move.location_id.usage == 'internal' and move.location_dest_id.usage != 'internal':
# fifo = (cost_method != 'lifo')
# #Ok -> do calculation based on quants
# price_amount = 0.0
# amount = 0.0
# #if move.id in quants???
# #search quants_move which are the quants associated with this move, which are not propagated quants
# quants_move = quant_obj.search(cr, uid, [('history_ids', 'in', move.id), ('propagated_from_id', '=', False)], context=context)
# for quant in quant_obj.browse(cr, uid, quants_move, context=context):
# price_amount += quant.qty * quant.price_unit
# amount += quant.qty
#
## tuples = product_obj.get_stock_matchings_fifolifo(cr, uid, [product.id], move_qty, fifo,
## move_uom, move.company_id.currency_id.id, context=ctx) #TODO Would be better to use price_currency_id for migration?
## price_amount = 0.0
## amount = 0.0
## #Write stock matchings
## for match in tuples:
## matchvals = {'move_in_id': match[0], 'qty': match[1],
## 'move_out_id': move.id}
## match_id = matching_obj.create(cr, uid, matchvals, context=context)
## res[move.id].append(match_id)
## price_amount += match[1] * match[2]
## amount += match[1]
# #Write price on out move
# if product_avail[product.id] >= product_uom_qty and product.cost_method in ['real']:
# if amount > 0:
# self.write(cr, uid, move.id, {'price_unit': price_amount / move_qty}, context=context) #Should be converted
# product_obj.write(cr, uid, product.id, {'standard_price': price_amount / amount}, context=ctx)
# else:
# pass
## raise osv.except_osv(_('Error'), _("Something went wrong finding quants ") + str(self.search(cr, uid, [('company_id','=', company_id), ('qty_remaining', '>', 0), ('state', '=', 'done'),
## ('location_id.usage', '!=', 'internal'), ('location_dest_id.usage', '=', 'internal'), ('product_id', '=', product.id)],
## order = 'date, id', context=context)) + str(move_qty) + str(move_uom) + str(move.company_id.currency_id.id))
# else:
# new_price = uom_obj._compute_price(cr, uid, product.uom_id.id, product.standard_price, move_uom)
# self.write(cr, uid, move.id, {'price_unit': new_price}, context=ctx)
# #Adjust product_avail when not average and move returned from
# if product.cost_method != 'average':
# product_avail[product.id] -= product_uom_qty
#
# #Check if in => if price 0.0, take standard price / Update price when average price and price on move != standard price
# if move.location_id.usage != 'internal' and move.location_dest_id.usage == 'internal':
# if move.price_unit == 0.0:
# new_price = uom_obj._compute_price(cr, uid, product.uom_id.id, product.standard_price, move_uom)
# self.write(cr, uid, move.id, {'price_unit': new_price}, context=ctx)
# elif product.cost_method == 'average':
# move_product_price = uom_obj._compute_price(cr, uid, move_uom, move.price_unit, product.uom_id.id)
# if product_avail[product.id] > 0.0:
# amount_unit = product.standard_price
# new_std_price = ((amount_unit * product_avail[product.id])\
# + (move_product_price * product_uom_qty))/(product_avail[product.id] + product_uom_qty)
# else:
# new_std_price = move_product_price
# product_obj.write(cr, uid, [product.id], {'standard_price': new_std_price}, context=ctx)
# # Should create the stock move matchings for previous outs for the negative stock that can be matched with is in
# if product_avail[product.id] < 0.0: #TODO LATER
# resneg = self._generate_negative_stock_matchings(cr, uid, [move.id], product, quants[move.id], context=ctx)
# res[move.id] += resneg
# product_avail[product.id] += product_uom_qty
# return res
# FIXME: needs refactoring, this code is partially duplicated in stock_picking.do_partial()!
def do_partial(self, cr, uid, ids, partial_datas, context=None):

View File

@ -37,7 +37,6 @@
uom_po_id: product.product_uom_kgm
property_stock_inventory: location_opening
valuation: real_time
cost_method: average
description: Ice cream can be mass-produced and thus is widely available in developed parts of the world. Ice cream can be purchased in large cartons (vats and squrounds) from supermarkets and grocery stores, in smaller quantities from ice cream shops, convenience stores, and milk bars, and in individual servings from small carts or vans at public events.
-

View File

@ -78,9 +78,9 @@ class stock_partial_move(osv.osv_memory):
'lot_id': move.lot_id.id,
}
moves_ids.append(move_id)
if (move.move_id.picking_id.type == 'in') and (move.product_id.cost_method != 'standard'):
partial_data['move%s' % (move_id)].update(product_price=move.cost,
product_currency=move.currency.id)
#if (move.move_id.picking_id.type == 'in') and (move.product_id.cost_method != 'standard'):
# partial_data['move%s' % (move_id)].update(product_price=move.cost,
# product_currency=move.currency.id)
self.pool.get('stock.move').do_partial(cr, uid, moves_ids, partial_data, context=context)
return {'type': 'ir.actions.act_window_close'}

View File

@ -124,24 +124,24 @@ class stock_partial_picking(osv.osv_memory):
res.update(date=time.strftime(DEFAULT_SERVER_DATETIME_FORMAT))
return res
def _product_cost_for_average_update(self, cr, uid, move):
"""Returns product cost and currency ID for the given move, suited for re-computing
the average product cost.
#def _product_cost_for_average_update(self, cr, uid, move):
# """Returns product cost and currency ID for the given move, suited for re-computing
# the average product cost.
:return: map of the form::
# :return: map of the form::
{'cost': 123.34,
'currency': 42}
"""
# Currently, the cost on the product form is supposed to be expressed in the currency
# of the company owning the product. If not set, we fall back to the picking's company,
# which should work in simple cases.
# need to take the standard_price from the correct company
move2 = self.pool.get("stock.move").browse(cr, uid, move.id, context={'force_company': move.company_id.id})
return {'cost': move2.product_id.standard_price,
'currency': False}
# {'cost': 123.34,
# 'currency': 42}
# """
# # Currently, the cost on the product form is supposed to be expressed in the currency
# # of the company owning the product. If not set, we fall back to the picking's company,
# # which should work in simple cases.
#
# # need to take the standard_price from the correct company
# move2 = self.pool.get("stock.move").browse(cr, uid, move.id, context={'force_company': move.company_id.id})
#
# return {'cost': move2.product_id.standard_price,
# 'currency': False}
def _partial_move_for(self, cr, uid, move):
partial_move = {
@ -151,9 +151,10 @@ class stock_partial_picking(osv.osv_memory):
'move_id': move.id,
'location_id': move.location_id.id,
'location_dest_id': move.location_dest_id.id,
'cost': move.product_id.standard_price,
}
if move.picking_id.type == 'in' and move.product_id.cost_method != 'standard':
partial_move.update(update_cost=True, **self._product_cost_for_average_update(cr, uid, move))
# if move.picking_id.type == 'in' and move.product_id.cost_method != 'standard':
# partial_move.update(update_cost=True, **self._product_cost_for_average_update(cr, uid, move))
return partial_move
def do_partial(self, cr, uid, ids, context=None):
@ -211,8 +212,8 @@ class stock_partial_picking(osv.osv_memory):
'product_uom': wizard_line.product_uom.id,
'lot_id': wizard_line.lot_id.id,
}
if (picking_type == 'in') and (wizard_line.product_id.cost_method != 'standard'):
partial_data['move%s' % (wizard_line.move_id.id)].update(product_price=wizard_line.cost,)
#if (picking_type == 'in') and (wizard_line.product_id.cost_method != 'standard'):
# partial_data['move%s' % (wizard_line.move_id.id)].update(product_price=wizard_line.cost,)
stock_picking.do_partial(cr, uid, [partial.picking_id.id], partial_data, context=context)
return {'type': 'ir.actions.act_window_close'}

View File

@ -183,6 +183,12 @@ 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
FIFO Price: The cost price is recomputed at each outgoing shipment FIFO
LIFO Price: The cost price is recomputed at each outgoing shipment LIFO""",
string="Costing Method", required=True),
'property_stock_account_input': fields.property(
type='many2one',
relation='account.account',
@ -197,6 +203,10 @@ class product_template(osv.osv):
"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."),
}
_defaults = {
'cost_method': 'standard',
}
class product_category(osv.osv):
_inherit = 'product.category'

View File

@ -43,6 +43,10 @@
</group>
</page>
</page>
<xpath expr="//field[@name='standard_price']" position='replace'>
<field name="standard_price" attrs="{'readonly':[('cost_method','=','average')]}"/>
<field name="cost_method"/>
</xpath>
</field>
</record>
@ -52,6 +56,7 @@
<field name="inherit_id" ref="product.product_normal_form_view"/>
<field name="arch" type="xml">
<field name="standard_price" position="replace" version="7.0">
<field name="cost_method" groups="product.group_costing_method"/>
<label string="Cost Price" for="standard_price" align="1.0" groups="base.group_user"/>
<div groups="base.group_user">
<field name="standard_price" attrs="{'readonly':[('valuation','=','real_time'), ('cost_method', 'in', ['standard', 'average'])]}" nolabel="1"/>

View File

@ -80,63 +80,65 @@ class stock_quant(osv.osv):
if company_from == company_to:
return False
journal_id, acc_src, acc_dest, acc_valuation = self._get_accounting_data_fir_valuation(cr, uid, move, context=context)
account_moves = []
# Create Journal Entry for products arriving in the company
if company_to:
pass
if location_from.usage == 'customer':
#goods returned from customer
account_moves += self._create_account_move_line(cr, uid, quant, acc_dest, acc_valuation, context=context)
else:
account_moves += self._create_account_move_line(cr, uid, quant, acc_src, acc_valuation, context=context)
# Create Journal Entry for products leaving the company
if company_from:
pass
if location_to.usage == 'supplier':
#goods returned to supplier
account_moves += self._create_account_move_line(cr, uid, quant, acc_valuation, acc_src, context=context)
else:
account_moves += self._create_account_move_line(cr, uid, quant, acc_valuation, acc_dest, context=context)
def move_single_quant(self, cr, uid, quant, qty, move, context=None):
location_from = quant.location_id
super(stock_quant, self).move_single_quant(cr, uid, quant, qty, move, context=context)
self._account_entry_move(cr, uid, quant, location_from, location_to, move, context=context)
quant.refresh()
self._account_entry_move(cr, uid, quant, location_from, quant.location_id, move, context=context)
# TODO: move this code on the _account_entry_move method above. Should be simpler
#
#def _get_accounting_data_for_valuation(self, cr, uid, move, context=None):
# """
# Return the accounts and journal to use to post Journal Entries for the real-time
# valuation of the move.
def _get_accounting_data_for_valuation(self, cr, uid, move, context=None):
"""
Return the accounts and journal to use to post Journal Entries for the real-time
valuation of the quant.
# :param context: context dictionary that can explicitly mention the company to consider via the 'force_company' key
# :raise: osv.except_osv() is any mandatory account or journal is not defined.
# """
# product_obj=self.pool.get('product.product')
# accounts = product_obj.get_product_accounts(cr, uid, move.product_id.id, context)
# if move.location_id.valuation_out_account_id:
# acc_src = move.location_id.valuation_out_account_id.id
# else:
# acc_src = accounts['stock_account_input']
:param context: context dictionary that can explicitly mention the company to consider via the 'force_company' key
:returns: journal_id, source account, destination account, valuation account
:raise: osv.except_osv() is any mandatory account or journal is not defined.
"""
product_obj=self.pool.get('product.product')
accounts = product_obj.get_product_accounts(cr, uid, move.product_id.id, context)
if move.location_id.valuation_out_account_id:
acc_src = move.location_id.valuation_out_account_id.id
else:
acc_src = accounts['stock_account_input']
# if move.location_dest_id.valuation_in_account_id:
# acc_dest = move.location_dest_id.valuation_in_account_id.id
# else:
# acc_dest = accounts['stock_account_output']
if move.location_dest_id.valuation_in_account_id:
acc_dest = move.location_dest_id.valuation_in_account_id.id
else:
acc_dest = accounts['stock_account_output']
# acc_valuation = accounts.get('property_stock_valuation_account_id', False)
# journal_id = accounts['stock_journal']
acc_valuation = accounts.get('property_stock_valuation_account_id', False)
journal_id = accounts['stock_journal']
# if acc_dest == acc_valuation:
# raise osv.except_osv(_('Error!'), _('Cannot create Journal Entry, Output Account of this product and Valuation account on category of this product are same.'))
# if acc_src == acc_valuation:
# raise osv.except_osv(_('Error!'), _('Cannot create Journal Entry, Input Account of this product and Valuation account on category of this product are same.'))
# if not acc_src:
# raise osv.except_osv(_('Error!'), _('Please define stock input account for this product or its category: "%s" (id: %d)') % \
# (move.product_id.name, move.product_id.id,))
# if not acc_dest:
# raise osv.except_osv(_('Error!'), _('Please define stock output account for this product or its category: "%s" (id: %d)') % \
# (move.product_id.name, move.product_id.id,))
# if not journal_id:
# raise osv.except_osv(_('Error!'), _('Please define journal on the product category: "%s" (id: %d)') % \
# (move.product_id.categ_id.name, move.product_id.categ_id.id,))
# if not acc_valuation:
# raise osv.except_osv(_('Error!'), _('Please define inventory valuation account on the product category: "%s" (id: %d)') % \
# (move.product_id.categ_id.name, move.product_id.categ_id.id,))
# return journal_id, acc_src, acc_dest, acc_valuation
if not all([acc_src, acc_dest, acc_valuation, journal_id]):
raise osv.except_osv(_('Error!'), _('''One of the following information is missing on the product or product category and prevents the accounting valuation entries to be created:
Stock Input Account: %s
Stock Output Account: %s
Stock Valuation Account: %s
Stock Journal: %s
''') % (acc_src, acc_dest, acc_valuation, journal_id))
return journal_id, acc_src, acc_dest, acc_valuation
##We can use a preliminary type
@ -250,51 +252,37 @@ class stock_quant(osv.osv):
# 'line_id': move_lines,
# 'ref': move.picking_id and move.picking_id.name})
#def _create_account_move_line(self, cr, uid, quant, src_account_id, dest_account_id, context=None):
# """
# Generate the account.move.line values to post to track the stock valuation difference due to the
# processing of the given stock move.
# """
# move_list = []
# # Consists of access rights
# # TODO Check if amount_currency is not needed
# match_obj = self.pool.get("stock.move.matching")
# if type == 'out' and move.product_id.cost_method in ['real']:
# for match in match_obj.browse(cr, uid, matches, context=context):
# move_list += [(match.qty, match.qty * match.price_unit_out)]
# elif type == 'in' and move.product_id.cost_method in ['real']:
# move_list = [(move.product_qty, reference_amount)]
# else:
# move_list = [(move.product_qty, reference_amount)]
# res = []
# for item in move_list:
# # prepare default values considering that the destination accounts have the reference_currency_id as their main currency
# partner_id = (move.picking_id.partner_id and self.pool.get('res.partner')._find_accounting_partner(move.picking_id.partner_id).id) or False
# debit_line_vals = {
# 'name': move.name,
# 'product_id': move.product_id and move.product_id.id or False,
# 'quantity': item[0],
# 'product_uom_id': move.product_uom.id,
# 'ref': move.picking_id and move.picking_id.name or False,
# 'date': time.strftime('%Y-%m-%d'),
# 'partner_id': partner_id,
# 'debit': item[1],
# 'account_id': dest_account_id,
# }
# credit_line_vals = {
# 'name': move.name,
# 'product_id': move.product_id and move.product_id.id or False,
# 'quantity': item[0],
# 'product_uom_id': move.product_uom.id,
# 'ref': move.picking_id and move.picking_id.name or False,
# 'date': time.strftime('%Y-%m-%d'),
# 'partner_id': partner_id,
# 'credit': item[1],
# 'account_id': src_account_id,
# }
# res += [(0, 0, debit_line_vals), (0, 0, credit_line_vals)]
# return res
def _create_account_move_line(self, cr, uid, quant, move, credit_account_id, debit_account_id, context=None):
"""
Generate the account.move.line values to post to track the stock valuation difference due to the
processing of the given quant.
"""
valuation_amount = quant.product_id.cost_method == 'real' and quant.cost or quant.product_id.standard_price
partner_id = (move.picking_id.partner_id and self.pool.get('res.partner')._find_accounting_partner(move.picking_id.partner_id).id) or False
debit_line_vals = {
'name': move.name,
'product_id': quant.product_id.id,
'quantity': quant.product_qty,
'product_uom_id': quant.product_id.product_uom.id,
'ref': move.picking_id and move.picking_id.name or False,
'date': time.strftime('%Y-%m-%d'),
'partner_id': partner_id,
'debit': valuation_amount,
'account_id': debit_account_id,
}
credit_line_vals = {
'name': move.name,
'product_id': quant.product_id.id,
'quantity': quant.product_qty,
'product_uom_id': quant.product_id.product_uom.id,
'ref': move.picking_id and move.picking_id.name or False,
'date': time.strftime('%Y-%m-%d'),
'partner_id': partner_id,
'credit': valuation_amount,
'account_id': credit_account_id,
}
res += [(0, 0, debit_line_vals), (0, 0, credit_line_vals)]
return res
#----------------------------------------------------------
@ -315,6 +303,8 @@ class stock_picking(osv.osv):
'invoice_state': 'none',
}
#TODO update standard price on product after do_partial()
#TODO: we don't need to change invoice_state on cancelation, do we?
#def action_cancel(self, cr, uid, ids, context=None):
# """ Changes picking state to cancel.
@ -640,6 +630,12 @@ class stock_move(osv.osv):
# return True
class report_stock_inventory(osv.osv):
_inherit = "report.stock.inventory"
def _get_inventory_value(self, cr, uid, line, prodbrow, context=None):
if prodbrow[(line.company_id.id, line.product_id.id)].cost_method in ('real'):
return line.value
return super(report_stock_inventory, self)._get_inventory_value(cr, uid, line, prodbrow, context=context)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -2,7 +2,6 @@
In order to test the stock_location module, I create a product and define its pulled flows. If a product is needed either in Chicago Shop or in Birmingham shop, there must be a incomming move from transit location to these shops, and an outgoing move from Stock to the transit location.
-
!record {model: product.product, id: product_product_hpcdwriters01}:
cost_method: standard
list_price: 1000.0
mes_type: fixed
name: HP CD writers

View File

@ -29,7 +29,6 @@
Stock Input to Quality test and Quality test -Stock .
-
!record {model: product.product, id: product_product_hpcdwriters01}:
cost_method: standard
list_price: 1000.0
mes_type: fixed
name: HP CD writers