[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:
parent
c77e66bdc3
commit
63111b6510
|
@ -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',
|
||||
}
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -38,15 +38,15 @@ OpenERP’s 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',
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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.
|
||||
|
||||
-
|
||||
|
|
|
@ -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'}
|
||||
|
||||
|
|
|
@ -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'}
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue