}
stock_journal()
+
#----------------------------------------------------------
# Stock Location
#----------------------------------------------------------
_description = "Location"
_parent_name = "location_id"
_parent_store = True
- _parent_order = 'name'
+ _parent_order = 'posz,name'
_order = 'parent_left'
def name_get(self, cr, uid, ids, context=None):
'stock_virtual_value': fields.function(_product_value, method=True, type='float', string='Virtual Stock Value', multi="stock", digits_compute=dp.get_precision('Account')),
'company_id': fields.many2one('res.company', 'Company', select=1, help='Let this field empty if this location is shared between all companies'),
'scrap_location': fields.boolean('Scrap Location', help='Check this box to allow using this location to put scrapped/damaged goods.'),
+ 'valuation_in_account_id': fields.many2one('account.account', 'Stock Input Account',domain = [('type','=','other')], help='This account will be used to value stock moves that have this location as destination, instead of the stock output account from the product.'),
+ 'valuation_out_account_id': fields.many2one('account.account', 'Stock Output Account',domain = [('type','=','other')], help='This account will be used to value stock moves that have this location as source, instead of the stock input account from the product.'),
}
_defaults = {
'active': True,
context['compute_child'] = False
if not product_ids:
- product_ids = product_obj.search(cr, uid, [])
+ product_ids = product_obj.search(cr, uid, [], context={'active_test': False})
products = product_obj.browse(cr, uid, product_ids, context=context)
products_by_uom = {}
def _product_virtual_get(self, cr, uid, id, product_ids=False, context=None, states=['done']):
return self._product_all_get(cr, uid, id, product_ids, context, ['confirmed', 'waiting', 'assigned', 'done'])
+ def _try_lock_product_reserve(self, cr, uid, location_ids, product_id, product_qty, context=None):
+ try:
+ # Must lock with a separate select query than the ones used in _product_reserve
+ # because FOR UPDATE can't be used with aggregation/group by's
+ # (i.e. when individual rows aren't identifiable).
+ # We use a SAVEPOINT to be able to rollback this part of the transaction without
+ # failing the whole transaction in case the LOCK cannot be acquired.
+ cr.execute("SAVEPOINT stock_location_product_reserve")
+ # We lock all stock moves in states we are going to consider in the
+ # calculation. By locking all DONE move we prevent other transactions
+ # from reserving the same products, as they won't be allowed to SELECT
+ # them until we're done.
+ cr.execute("""SELECT id FROM stock_move
+ WHERE product_id=%s
+ AND (
+ (location_dest_id IN %s AND state = 'done')
+ OR
+ (location_id IN %s AND state in ('done', 'assigned'))
+ )
+ FOR UPDATE of stock_move NOWAIT""",
+ (product_id, location_ids, location_ids), log_exceptions=False)
+ except Exception:
+ # Here it's likely that the FOR UPDATE NOWAIT failed to get the LOCK,
+ # so we ROLLBACK to the SAVEPOINT to restore the transaction to its earlier
+ # state, we return False as if the products were not available, and log it:
+ cr.execute("ROLLBACK TO stock_location_product_reserve")
+ logger = logging.getLogger('stock.location')
+ logger.warn("Failed attempt to reserve %s x product %s, likely due to another transaction already in progress. Next attempt is likely to work. Detailed error available at DEBUG level.", product_qty, product_id)
+ logger.debug("Trace of the failed product reservation attempt: ", exc_info=True)
+ return False
+ return True
+
def _product_reserve(self, cr, uid, ids, product_id, product_qty, context=None, lock=False):
"""
Attempt to find a quantity ``product_qty`` (in the product's default uom or the uom passed in ``context``) of product ``product_id``
in locations with id ``ids`` and their child locations. If ``lock`` is True, the stock.move lines
of product with id ``product_id`` in the searched location will be write-locked using Postgres's
- "FOR UPDATE NOWAIT" option until the transaction is committed or rolled back, to prevent reservin
+ "FOR UPDATE NOWAIT" option until the transaction is committed or rolled back, to prevent reserving
twice the same products.
If ``lock`` is True and the lock cannot be obtained (because another transaction has locked some of
the same stock.move lines), a log line will be output and False will be returned, as if there was
not enough stock.
:param product_id: Id of product to reserve
- :param product_qty: Quantity of product to reserve (in the product's default uom or the uom passed in ``context``)
+ :param product_qty: Quantity of product to reserve (in the uom passed in ``context``)
:param lock: if True, the stock.move lines of product with id ``product_id`` in all locations (and children locations) with ``ids`` will
be write-locked using postgres's "FOR UPDATE NOWAIT" option until the transaction is committed or rolled back. This is
to prevent reserving twice the same products.
- :param context: optional context dictionary: it a 'uom' key is present it will be used instead of the default product uom to
- compute the ``product_qty`` and in the return value.
- :return: List of tuples in the form (qty, location_id) with the (partial) quantities that can be taken in each location to
- reach the requested product_qty (``qty`` is expressed in the default uom of the product), of False if enough
- products could not be found, or the lock could not be obtained (and ``lock`` was True).
+ :param context: context dictionary with 'uom' key mapped to the ID of the UoM to use to compute the product quantities
+ :return: List of pairs (qty, location_id) with the (partial) quantities that can be taken in each location to
+ reach the requested product_qty (expressed in the requested uom), or False if not enough
+ products could be found, or the lock could not be obtained (and ``lock`` was True).
+ sum(qty) == ``product_qty``.
"""
- result = []
- amount = 0.0
if context is None:
context = {}
- for id in self.search(cr, uid, [('location_id', 'child_of', ids)]):
- if lock:
- try:
- # Must lock with a separate select query because FOR UPDATE can't be used with
- # aggregation/group by's (when individual rows aren't identifiable).
- # We use a SAVEPOINT to be able to rollback this part of the transaction without
- # failing the whole transaction in case the LOCK cannot be acquired.
- cr.execute("SAVEPOINT stock_location_product_reserve")
- cr.execute("""SELECT id FROM stock_move
- WHERE product_id=%s AND
- (
- (location_dest_id=%s AND
- location_id<>%s AND
- state='done')
- OR
- (location_id=%s AND
- location_dest_id<>%s AND
- state in ('done', 'assigned'))
- )
- FOR UPDATE of stock_move NOWAIT""", (product_id, id, id, id, id), log_exceptions=False)
- except Exception:
- # Here it's likely that the FOR UPDATE NOWAIT failed to get the LOCK,
- # so we ROLLBACK to the SAVEPOINT to restore the transaction to its earlier
- # state, we return False as if the products were not available, and log it:
- cr.execute("ROLLBACK TO stock_location_product_reserve")
- logger = logging.getLogger('stock.location')
- logger.warn("Failed attempt to reserve %s x product %s, likely due to another transaction already in progress. Next attempt is likely to work. Detailed error available at DEBUG level.", product_qty, product_id)
- logger.debug("Trace of the failed product reservation attempt: ", exc_info=True)
- return False
+ location_ids = self.search(cr, uid, [('location_id', 'child_of', ids)])
+ locations_tuple = tuple(location_ids)
+ if lock and not self._try_lock_product_reserve(cr, uid, locations_tuple, product_id, product_qty, context=context):
+ return False
- # XXX TODO: rewrite this with one single query, possibly even the quantity conversion
- cr.execute("""SELECT product_uom, sum(product_qty) AS product_qty
- FROM stock_move
- WHERE location_dest_id=%s AND
- location_id<>%s AND
- product_id=%s AND
- state='done'
- GROUP BY product_uom
- """,
- (id, id, product_id))
- results = cr.dictfetchall()
- cr.execute("""SELECT product_uom,-sum(product_qty) AS product_qty
- FROM stock_move
- WHERE location_id=%s AND
- location_dest_id<>%s AND
- product_id=%s AND
- state in ('done', 'assigned')
- GROUP BY product_uom
- """,
- (id, id, product_id))
- results += cr.dictfetchall()
-
- total = 0.0
- results2 = 0.0
- for r in results:
- amount = self.pool.get('product.uom')._compute_qty(cr, uid, r['product_uom'], r['product_qty'], context.get('uom', False))
- results2 += amount
- total += amount
-
- if total <= 0.0:
+ # Giant query to obtain triplets of (product_uom, product_qty, location_id) summing all relevant
+ # stock moves quantities per location, with incoming quantities taken positive,
+ # and outgoing taken negative.
+ cr.execute("""SELECT x.product_uom, SUM(x.coeff * x.product_qty) as product_qty, x.loc_id as location_id
+ FROM (
+ SELECT 1.0 as coeff, product_uom, location_dest_id as loc_id,
+ sum(product_qty) AS product_qty
+ FROM stock_move
+ WHERE location_dest_id in %s AND
+ location_id != location_dest_id AND
+ product_id = %s AND
+ state = 'done'
+ GROUP BY location_dest_id, product_uom
+ UNION
+ SELECT -1.0 as coeff, product_uom, location_id as loc_id,
+ sum(product_qty) AS product_qty
+ FROM stock_move
+ WHERE location_id in %s AND
+ location_id != location_dest_id AND
+ product_id = %s AND
+ state in ('done', 'assigned')
+ GROUP BY location_id, product_uom
+ ) AS x
+ GROUP BY x.loc_id, x.product_uom
+ """,
+ (locations_tuple, product_id, locations_tuple, product_id))
+ sum_rows = cr.fetchall()
+
+ qty_by_location = {}
+ ProductUom = self.pool.get('product.uom')
+ target_uom = context.get('uom')
+ # Convert all UoMs into target UoM
+ for uom_id, qty, loc_id in sum_rows:
+ qty_by_location.setdefault(loc_id,0.0)
+ qty_by_location[loc_id] += ProductUom._compute_qty(cr, uid, uom_id, qty, target_uom)
+
+ # to compute final result we handle locations in the
+ # order in which they were returned by the original search().
+ result = []
+ for loc_id in location_ids:
+ if loc_id not in qty_by_location:
+ #skip location without this product
continue
-
- amount = results2
- if amount > 0:
- if amount > min(total, product_qty):
- amount = min(product_qty, total)
- result.append((amount, id))
- product_qty -= amount
- total -= amount
- if product_qty <= 0.0:
- return result
- if total <= 0.0:
- continue
+ qty = qty_by_location[loc_id]
+ if qty <= 0.0:
+ continue
+ qty = min(product_qty, qty)
+ result.append((qty, loc_id))
+ product_qty -= qty
+ if product_qty <= 0.0:
+ return result
return False
stock_location()
def make_sscc(self, cr, uid, context=None):
sequence = self.pool.get('ir.sequence').get(cr, uid, 'stock.lot.tracking')
- return sequence + str(self.checksum(sequence))
+ try:
+ return sequence + str(self.checksum(sequence))
+ except Exception:
+ return sequence
_columns = {
'name': fields.char('Pack Reference', size=64, required=True, select=True),
_defaults = {
'active': 1,
'name': make_sscc,
- 'date': time.strftime('%Y-%m-%d %H:%M:%S'),
+ 'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
}
def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
@param context: A standard dictionary
@return: A dictionary of values
"""
- value={}
- value=self.pool.get('action.traceability').action_traceability(cr,uid,ids,context)
+ value = {}
+ value = self.pool.get('action.traceability').action_traceability(cr,uid,ids,context)
return value
stock_tracking()
'move_type': 'direct',
'type': 'in',
'invoice_state': 'none',
- 'date': time.strftime('%Y-%m-%d %H:%M:%S'),
+ 'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.picking', context=c)
}
def action_process(self, cr, uid, ids, context=None):
+ if context is None: context = {}
+ partial_id = self.pool.get("stock.partial.picking").create(
+ cr, uid, {}, context=dict(context, active_ids=ids))
return {
'name':_("Products to Process"),
'view_mode': 'form',
'view_id': False,
'view_type': 'form',
'res_model': 'stock.partial.picking',
+ 'res_id': partial_id,
'type': 'ir.actions.act_window',
'nodestroy': True,
'target': 'new',
'domain': '[]',
- 'context': {
- 'active_id': ids[0],
- 'active_ids':ids
- }
+ 'context': dict(context, active_ids=ids)
}
def copy(self, cr, uid, id, default=None, context=None):
default = {}
default = default.copy()
picking_obj = self.browse(cr, uid, id, context=context)
- move_obj=self.pool.get('stock.move')
+ move_obj = self.pool.get('stock.move')
if ('name' not in default) or (picking_obj.name=='/'):
seq_obj_name = 'stock.picking.' + picking_obj.type
default['name'] = self.pool.get('ir.sequence').get(cr, uid, seq_obj_name)
default['origin'] = ''
default['backorder_id'] = False
- res=super(stock_picking, self).copy(cr, uid, id, default, context)
+ res = super(stock_picking, self).copy(cr, uid, id, default, context)
if res:
picking_obj = self.browse(cr, uid, res, context=context)
for move in picking_obj.move_lines:
""" Changes state of picking to available if all moves are confirmed.
@return: True
"""
+ move_obj = self.pool.get('stock.move')
for pick in self.browse(cr, uid, ids):
move_ids = [x.id for x in pick.move_lines if x.state == 'confirmed']
if not move_ids:
raise osv.except_osv(_('Warning !'),_('Not enough stock, unable to reserve the products.'))
- self.pool.get('stock.move').action_assign(cr, uid, move_ids)
+ move_obj.action_assign(cr, uid, move_ids)
return True
def force_assign(self, cr, uid, ids, *args):
@return: True
"""
wf_service = netsvc.LocalService("workflow")
+ move_obj = self.pool.get('stock.move')
for pick in self.browse(cr, uid, ids):
move_ids = [x.id for x in pick.move_lines if x.state in ['confirmed','waiting']]
- self.pool.get('stock.move').force_assign(cr, uid, move_ids)
+ move_obj.force_assign(cr, uid, move_ids)
wf_service.trg_write(uid, 'stock.picking', pick.id, cr)
return True
""" Validates picking directly from draft state.
@return: True
"""
- if context is None:
- context = {}
wf_service = netsvc.LocalService("workflow")
+ move_obj = self.pool.get('stock.move')
self.draft_force_assign(cr, uid, ids)
for pick in self.browse(cr, uid, ids, context=context):
move_ids = [x.id for x in pick.move_lines]
- self.pool.get('stock.move').force_assign(cr, uid, move_ids)
+ move_obj.force_assign(cr, uid, move_ids)
wf_service.trg_write(uid, 'stock.picking', pick.id, cr)
- context.update({'active_ids':ids})
- return {
- 'name': 'Make Picking',
- 'view_type': 'form',
- 'view_mode': 'form',
- 'res_model': 'stock.partial.picking',
- 'type': 'ir.actions.act_window',
- 'target': 'new',
- 'nodestroy': True,
- 'context':context
- }
+ return self.action_process(
+ cr, uid, ids, context=context)
def cancel_assign(self, cr, uid, ids, *args):
""" Cancels picking and moves.
@return: True
"""
wf_service = netsvc.LocalService("workflow")
+ move_obj = self.pool.get('stock.move')
for pick in self.browse(cr, uid, ids):
move_ids = [x.id for x in pick.move_lines]
- self.pool.get('stock.move').cancel_assign(cr, uid, move_ids)
+ move_obj.cancel_assign(cr, uid, move_ids)
wf_service.trg_write(uid, 'stock.picking', pick.id, cr)
return True
""" Tests whether the move is in done or cancel state or not.
@return: True or False
"""
- move_ids = self.pool.get('stock.move').search(cr, uid, [('picking_id', 'in', ids)])
- for move in self.pool.get('stock.move').browse(cr, uid, move_ids):
+ move_obj = self.pool.get('stock.move')
+ move_ids = move_obj.search(cr, uid, [('picking_id', 'in', ids)])
+ for move in move_obj.browse(cr, uid, move_ids):
if move.state not in ('done', 'cancel'):
+
if move.product_qty != 0.0:
return False
else:
- move.write(cr, uid, [move.id], {'state': 'done'})
+ move.write({'state': 'done'})
return True
def test_assigned(self, cr, uid, ids):
""" Changes picking state to cancel.
@return: True
"""
+ move_obj = self.pool.get('stock.move')
for pick in self.browse(cr, uid, ids, context=context):
ids2 = [move.id for move in pick.move_lines]
- self.pool.get('stock.move').action_cancel(cr, uid, ids2, context)
+ move_obj.action_cancel(cr, uid, ids2, context)
self.write(cr, uid, ids, {'state': 'cancel', 'invoice_state': 'none'})
self.log_picking(cr, uid, ids, context=context)
return True
""" Changes move state to assigned.
@return: True
"""
+ move_obj = self.pool.get('stock.move')
for pick in self.browse(cr, uid, ids, context=context):
todo = []
for move in pick.move_lines:
if move.state == 'assigned':
todo.append(move.id)
if len(todo):
- self.pool.get('stock.move').action_done(cr, uid, todo,
+ move_obj.action_done(cr, uid, todo,
context=context)
return True
"""
partner_obj = self.pool.get('res.partner')
partner = picking.address_id.partner_id
-
return partner_obj.address_get(cr, uid, [partner.id],
['contact', 'invoice'])
invoice_obj = self.pool.get('account.invoice')
invoice_line_obj = self.pool.get('account.invoice.line')
+ address_obj = self.pool.get('res.partner.address')
invoices_group = {}
res = {}
inv_type = type
if picking.invoice_state != '2binvoiced':
continue
payment_term_id = False
- partner = picking.address_id and picking.address_id.partner_id
+ partner = picking.address_id and picking.address_id.partner_id
if not partner:
raise osv.except_osv(_('Error, no partner !'),
_('Please put a partner on the picking list if you want to generate invoice.'))
address_contact_id, address_invoice_id = \
self._get_address_invoice(cr, uid, picking).values()
+ address = address_obj.browse(cr, uid, address_contact_id, context=context)
comment = self._get_comment_invoice(cr, uid, picking)
if group and partner.id in invoices_group:
'origin': (picking.name or '') + (picking.origin and (':' + picking.origin) or ''),
'type': inv_type,
'account_id': account_id,
- 'partner_id': partner.id,
+ 'partner_id': address.partner_id.id,
'address_invoice_id': address_invoice_id,
'address_contact_id': address_contact_id,
'comment': comment,
uos_id = move_line.product_uos and move_line.product_uos.id or False
if not uos_id and inv_type in ('out_invoice', 'out_refund'):
uos_id = move_line.product_uom.id
-
account_id = self.pool.get('account.fiscal.position').map_account(cr, uid, partner.property_account_position, account_id)
invoice_line_id = invoice_line_obj.create(cr, uid, {
'name': name,
complete, too_many, too_few = [], [], []
move_product_qty = {}
prodlot_ids = {}
+ product_avail = {}
for move in pick.move_lines:
if move.state in ('done', 'cancel'):
continue
- partial_data = partial_datas.get('move%s'%(move.id), False)
- assert partial_data, _('Missing partial picking data for move #%s') % (move.id)
- product_qty = partial_data.get('product_qty',0.0)
+ partial_data = partial_datas.get('move%s'%(move.id), {})
+ #Commented in order to process the less number of stock moves from partial picking wizard
+ #assert partial_data, _('Missing partial picking data for move #%s') % (move.id)
+ product_qty = partial_data.get('product_qty') or 0.0
move_product_qty[move.id] = product_qty
- product_uom = partial_data.get('product_uom',False)
- product_price = partial_data.get('product_price',0.0)
- product_currency = partial_data.get('product_currency',False)
- prodlot_id = partial_data.get('prodlot_id')
+ product_uom = partial_data.get('product_uom') or False
+ product_price = partial_data.get('product_price') or 0.0
+ product_currency = partial_data.get('product_currency') or False
+ prodlot_id = partial_data.get('prodlot_id') or False
prodlot_ids[move.id] = prodlot_id
if move.product_qty == product_qty:
complete.append(move)
move_currency_id = move.company_id.currency_id.id
context['currency_id'] = move_currency_id
qty = uom_obj._compute_qty(cr, uid, product_uom, product_qty, product.uom_id.id)
+
+ if product.id in product_avail:
+ product_avail[product.id] += qty
+ else:
+ product_avail[product.id] = product.qty_available
+
if qty > 0:
new_price = currency_obj.compute(cr, uid, product_currency,
move_currency_id, product_price)
else:
# Get the standard price
amount_unit = product.price_get('standard_price', context)[product.id]
- new_std_price = ((amount_unit * product.qty_available)\
- + (new_price * qty))/(product.qty_available + qty)
-
+ new_std_price = ((amount_unit * product_avail[product.id])\
+ + (new_price * qty))/(product_avail[product.id] + qty)
# Write the field according to price type field
product_obj.write(cr, uid, [product.id], {'standard_price': new_std_price})
if new_picking:
move_obj.write(cr, uid, [c.id for c in complete], {'picking_id': new_picking})
- for move in complete:
- if prodlot_ids.get(move.id):
- move_obj.write(cr, uid, move.id, {'prodlot_id': prodlot_ids[move.id]})
+ for move in complete:
+ if prodlot_ids.get(move.id):
+ move_obj.write(cr, uid, [move.id], {'prodlot_id': prodlot_ids[move.id]})
for move in too_many:
product_qty = move_product_qty[move.id]
defaults = {
@param ids: List of Picking Ids
@param context: A standard dictionary for contextual values
"""
+ if context is None:
+ context = {}
+ data_obj = self.pool.get('ir.model.data')
for pick in self.browse(cr, uid, ids, context=context):
msg=''
if pick.auto_picking:
'in':_('Reception'),
'internal': _('Internal picking'),
}
+ view_list = {
+ 'out': 'view_picking_out_form',
+ 'in': 'view_picking_in_form',
+ 'internal': 'view_picking_form',
+ }
message = type_list.get(pick.type, _('Document')) + " '" + (pick.name or '?') + "' "
if pick.min_date:
msg= _(' for the ')+ datetime.strptime(pick.min_date, '%Y-%m-%d %H:%M:%S').strftime('%m/%d/%Y')
'done': _('is done.'),
'draft':_('is in draft state.'),
}
+ res = data_obj.get_object_reference(cr, uid, 'stock', view_list.get(pick.type, 'view_picking_form'))
+ context.update({'view_id': res and res[1] or False})
message += state_list[pick.state]
- self.log(cr, uid, pick.id, message)
+ self.log(cr, uid, pick.id, message, context=context)
return True
stock_picking()
'move_ids': fields.one2many('stock.move', 'prodlot_id', 'Moves for this production lot', readonly=True),
}
_defaults = {
- 'date': time.strftime('%Y-%m-%d %H:%M:%S'),
+ 'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
'name': lambda x, y, z, c: x.pool.get('ir.sequence').get(y, z, 'stock.lot.serial'),
'product_id': lambda x, y, z, c: c.get('product_id', False),
}
@param context: A standard dictionary
@return: A dictionary of values
"""
- value=self.pool.get('action.traceability').action_traceability(cr,uid,ids,context)
+ value = self.pool.get('action.traceability').action_traceability(cr,uid,ids,context)
return value
stock_production_lot()
'indice': fields.char('Revision Number', size=16),
'author_id': fields.many2one('res.users', 'Author'),
'lot_id': fields.many2one('stock.production.lot', 'Production lot', select=True, ondelete='cascade'),
- 'company_id': fields.related('lot_id','company_id',type='many2one',relation='res.company',string='Company',store=True),
+ 'company_id': fields.related('lot_id','company_id',type='many2one',relation='res.company',string='Company', store=True, readonly=True),
}
_defaults = {
'author_id': lambda x, y, z, c: z,
- 'date': time.strftime('%Y-%m-%d'),
+ 'date': lambda *a: time.strftime('%Y-%m-%d'),
}
stock_production_lot_revision()
_order = 'date_expected desc, id'
_log_create = False
+ def action_partial_move(self, cr, uid, ids, context=None):
+ if context is None: context = {}
+ partial_id = self.pool.get("stock.partial.move").create(
+ cr, uid, {}, context=context)
+ return {
+ 'name':_("Products to Process"),
+ 'view_mode': 'form',
+ 'view_id': False,
+ 'view_type': 'form',
+ 'res_model': 'stock.partial.move',
+ 'res_id': partial_id,
+ 'type': 'ir.actions.act_window',
+ 'nodestroy': True,
+ 'target': 'new',
+ 'domain': '[]',
+ 'context': context
+ }
+
+
def name_get(self, cr, uid, ids, context=None):
res = []
for line in self.browse(cr, uid, ids, context=context):
_columns = {
'name': fields.char('Name', size=64, required=True, select=True),
'priority': fields.selection([('0', 'Not urgent'), ('1', 'Urgent')], 'Priority'),
- 'create_date': fields.datetime('Creation Date', readonly=True),
- 'date': fields.datetime('Date', required=True, help="Move date: scheduled date until move is done, then date of actual move processing", readonly=True),
- 'date_expected': fields.datetime('Scheduled Date', states={'done': [('readonly', True)]},required=True, help="Scheduled date for the processing of this move"),
+ 'create_date': fields.datetime('Creation Date', readonly=True, select=True),
+ 'date': fields.datetime('Date', required=True, select=True, help="Move date: scheduled date until move is done, then date of actual move processing", readonly=True),
+ 'date_expected': fields.datetime('Scheduled Date', states={'done': [('readonly', True)]},required=True, select=True, help="Scheduled date for the processing of this move"),
'product_id': fields.many2one('product.product', 'Product', required=True, select=True, domain=[('type','<>','service')],states={'done': [('readonly', True)]}),
'product_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product UoM'), required=True,states={'done': [('readonly', True)]}),
'move_history_ids2': fields.many2many('stock.move', 'stock_move_history_ids', 'child_id', 'parent_id', 'Move History (parent moves)'),
'picking_id': fields.many2one('stock.picking', 'Reference', select=True,states={'done': [('readonly', True)]}),
'note': fields.text('Notes'),
- 'state': fields.selection([('draft', 'Draft'), ('waiting', 'Waiting'), ('confirmed', 'Confirmed'), ('assigned', 'Available'), ('done', 'Done'), ('cancel', 'Cancelled')], 'State', readonly=True, select=True,
- help='When the stock move is created it is in the \'Draft\' state.\n After that it is set to \'Confirmed\' state.\n If stock is available state is set to \'Available\'.\n When the picking is done the state is \'Done\'.\
- \nThe state is \'Waiting\' if the move is waiting for another one.'),
+ 'state': fields.selection([('draft', 'Draft'), ('waiting', 'Waiting'), ('confirmed', 'Not Available'), ('assigned', 'Available'), ('done', 'Done'), ('cancel', 'Cancelled')], 'State', readonly=True, select=True,
+ help='When the stock move is created it is in the \'Draft\' state.\n After that, it is set to \'Not Available\' state if the scheduler did not find the products.\n When products are reserved it is set to \'Available\'.\n When the picking is done the state is \'Done\'.\
+ \nThe state is \'Waiting\' if the move is waiting for another one.'),
'price_unit': fields.float('Unit Price', digits_compute= dp.get_precision('Account'), help="Technical field used to record the product cost set by the user during a picking confirmation (when average price costing method is used)"),
'price_currency_id': fields.many2one('res.currency', 'Currency for average price', help="Technical field used to record the currency chosen by the user during a picking confirmation (when average price costing method is used)"),
'company_id': fields.many2one('res.company', 'Company', required=True, select=True),
'origin': fields.related('picking_id','origin',type='char', size=64, relation="stock.picking", string="Origin", store=True),
# used for colors in tree views:
- 'scrapped': fields.related('location_dest_id','scrap_location',type='boolean',relation='stock.location',string='Scrapped'),
+ 'scrapped': fields.related('location_dest_id','scrap_location',type='boolean',relation='stock.location',string='Scrapped', readonly=True),
}
_constraints = [
(_check_tracking,
except:
pass
if context.get('address_in_id', False):
- return self.pool.get('res.partner.address').browse(cr, uid, context['address_in_id'], context).partner_id.property_stock_supplier.id
+ part_obj_add = self.pool.get('res.partner.address').browse(cr, uid, context['address_in_id'], context=context)
+ if part_obj_add.partner_id:
+ return part_obj_add.partner_id.property_stock_supplier.id
return False
_defaults = {
'priority': '1',
'product_qty': 1.0,
'scrapped' : False,
- 'date': time.strftime('%Y-%m-%d %H:%M:%S'),
+ 'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.move', context=c),
- 'date_expected': time.strftime('%Y-%m-%d %H:%M:%S'),
+ 'date_expected': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
}
def write(self, cr, uid, ids, vals, context=None):
+ if isinstance(ids, (int, long)):
+ ids = [ids]
if uid != 1:
frozen_fields = set(['product_qty', 'product_uom', 'product_uos_qty', 'product_uos', 'location_id', 'location_dest_id', 'product_id'])
for move in self.browse(cr, uid, ids, context=context):
return res
def onchange_lot_id(self, cr, uid, ids, prodlot_id=False, product_qty=False,
- loc_id=False, product_id=False, context=None):
+ loc_id=False, product_id=False, uom_id=False, context=None):
""" On change of production lot gives a warning message.
@param prodlot_id: Changed production lot id
@param product_qty: Quantity of product
return {}
ctx = context and context.copy() or {}
ctx['location_id'] = loc_id
- prodlot = self.pool.get('stock.production.lot').browse(cr, uid, prodlot_id, ctx)
- location = self.pool.get('stock.location').browse(cr, uid, loc_id)
+ ctx.update({'raise-exception': True})
+ uom_obj = self.pool.get('product.uom')
+ product_obj = self.pool.get('product.product')
+ product_uom = product_obj.browse(cr, uid, product_id, context=ctx).uom_id
+ prodlot = self.pool.get('stock.production.lot').browse(cr, uid, prodlot_id, context=ctx)
+ location = self.pool.get('stock.location').browse(cr, uid, loc_id, context=ctx)
+ uom = uom_obj.browse(cr, uid, uom_id, context=ctx)
+ amount_actual = uom_obj._compute_qty_obj(cr, uid, product_uom, prodlot.stock_available, uom, context=ctx)
warning = {}
- if (location.usage == 'internal') and (product_qty > (prodlot.stock_available or 0.0)):
+ if (location.usage == 'internal') and (product_qty > (amount_actual or 0.0)):
warning = {
'title': _('Insufficient Stock in Lot !'),
- 'message': _('You are moving %.2f products but only %.2f available in this lot.') % (product_qty, prodlot.stock_available or 0.0)
+ 'message': _('You are moving %.2f %s products but only %.2f %s available in this lot.') % (product_qty, uom.name, amount_actual, uom.name)
}
return {'warning': warning}
result.setdefault(m.picking_id, [])
result[m.picking_id].append( (m, dest) )
return result
- def _create_chained_picking(self, cr, uid, pick_name,picking,ptype,move, context=None):
+
+ def _create_chained_picking(self, cr, uid, pick_name, picking, ptype, move, context=None):
res_obj = self.pool.get('res.company')
picking_obj = self.pool.get('stock.picking')
pick_id= picking_obj.create(cr, uid, {
'name': pick_name,
- 'origin': str(picking.origin or ''),
+ 'origin': tools.ustr(picking.origin or ''),
'type': ptype,
'note': picking.note,
'move_type': picking.move_type,
'date': picking.date,
})
return pick_id
+ def create_chained_picking(self, cr, uid, moves, context=None):
+ res_obj = self.pool.get('res.company')
+ location_obj = self.pool.get('stock.location')
+ move_obj = self.pool.get('stock.move')
+ picking_obj = self.pool.get('stock.picking')
+ wf_service = netsvc.LocalService("workflow")
+ new_moves = []
+ if context is None:
+ context = {}
+ seq_obj = self.pool.get('ir.sequence')
+ for picking, todo in self._chain_compute(cr, uid, moves, context=context).items():
+ ptype = todo[0][1][5] and todo[0][1][5] or location_obj.picking_type_get(cr, uid, todo[0][0].location_dest_id, todo[0][1][0])
+ if picking:
+ # name of new picking according to its type
+ new_pick_name = seq_obj.get(cr, uid, 'stock.picking.' + ptype)
+ pickid = self._create_chained_picking(cr, uid, new_pick_name, picking, ptype, todo, context=context)
+ # Need to check name of old picking because it always considers picking as "OUT" when created from Sale Order
+ old_ptype = location_obj.picking_type_get(cr, uid, picking.move_lines[0].location_id, picking.move_lines[0].location_dest_id)
+ if old_ptype != picking.type:
+ old_pick_name = seq_obj.get(cr, uid, 'stock.picking.' + old_ptype)
+ picking_obj.write(cr, uid, [picking.id], {'name': old_pick_name}, context=context)
+ else:
+ pickid = False
+ for move, (loc, dummy, delay, dummy, company_id, ptype) in todo:
+ new_id = move_obj.copy(cr, uid, move.id, {
+ 'location_id': move.location_dest_id.id,
+ 'location_dest_id': loc.id,
+ 'date_moved': time.strftime('%Y-%m-%d'),
+ 'picking_id': pickid,
+ 'state': 'waiting',
+ 'company_id': company_id or res_obj._company_default_get(cr, uid, 'stock.company', context=context) ,
+ 'move_history_ids': [],
+ 'date': (datetime.strptime(move.date, '%Y-%m-%d %H:%M:%S') + relativedelta(days=delay or 0)).strftime('%Y-%m-%d'),
+ 'move_history_ids2': []}
+ )
+ move_obj.write(cr, uid, [move.id], {
+ 'move_dest_id': new_id,
+ 'move_history_ids': [(4, new_id)]
+ })
+ new_moves.append(self.browse(cr, uid, [new_id])[0])
+ if pickid:
+ wf_service.trg_validate(uid, 'stock.picking', pickid, 'button_confirm', cr)
+ if new_moves:
+ new_moves += self.create_chained_picking(cr, uid, new_moves, context)
+ return new_moves
+
def action_confirm(self, cr, uid, ids, context=None):
""" Confirms stock move.
@return: List of ids.
"""
moves = self.browse(cr, uid, ids, context=context)
self.write(cr, uid, ids, {'state': 'confirmed'})
- res_obj = self.pool.get('res.company')
- location_obj = self.pool.get('stock.location')
- move_obj = self.pool.get('stock.move')
- wf_service = netsvc.LocalService("workflow")
- def create_chained_picking(self, cr, uid, moves, context=None):
- new_moves = []
- if context is None:
- context = {}
- for picking, todo in self._chain_compute(cr, uid, moves, context=context).items():
- ptype = todo[0][1][5] and todo[0][1][5] or location_obj.picking_type_get(cr, uid, todo[0][0].location_dest_id, todo[0][1][0])
- pick_name = picking.name or ''
- if picking:
- pickid = self._create_chained_picking(cr, uid, pick_name,picking,ptype,todo,context)
- else:
- pickid = False
- for move, (loc, dummy, delay, dummy, company_id, ptype) in todo:
- new_id = move_obj.copy(cr, uid, move.id, {
- 'location_id': move.location_dest_id.id,
- 'location_dest_id': loc.id,
- 'date_moved': time.strftime('%Y-%m-%d'),
- 'picking_id': pickid,
- 'state': 'waiting',
- 'company_id': company_id or res_obj._company_default_get(cr, uid, 'stock.company', context=context) ,
- 'move_history_ids': [],
- 'date': (datetime.strptime(move.date, '%Y-%m-%d %H:%M:%S') + relativedelta(days=delay or 0)).strftime('%Y-%m-%d'),
- 'move_history_ids2': []}
- )
- move_obj.write(cr, uid, [move.id], {
- 'move_dest_id': new_id,
- 'move_history_ids': [(4, new_id)]
- })
- new_moves.append(self.browse(cr, uid, [new_id])[0])
- if pickid:
- wf_service.trg_validate(uid, 'stock.picking', pickid, 'button_confirm', cr)
- if new_moves:
- create_chained_picking(self, cr, uid, new_moves, context)
- create_chained_picking(self, cr, uid, moves, context)
+ self.create_chained_picking(cr, uid, moves, context)
return []
def action_assign(self, cr, uid, ids, *args):
done.append(move.id)
pickings[move.picking_id.id] = 1
r = res.pop(0)
- cr.execute('update stock_move set location_id=%s, product_qty=%s where id=%s', (r[1], r[0], move.id))
+ cr.execute('update stock_move set location_id=%s, product_qty=%s, product_uos_qty=%s where id=%s', (r[1], r[0], r[0] * move.product_id.uos_coeff, move.id))
while res:
r = res.pop(0)
- move_id = self.copy(cr, uid, move.id, {'product_qty': r[0], 'location_id': r[1]})
+ move_id = self.copy(cr, uid, move.id, {'product_qty': r[0],'product_uos_qty': r[0] * move.product_id.uos_coeff,'location_id': r[1]})
done.append(move_id)
if done:
count += len(done)
if context is None:
context = {}
pickings = {}
+ picking_obj = self.pool.get('stock.picking')
for move in self.browse(cr, uid, ids, context=context):
if move.state in ('confirmed', 'waiting', 'assigned', 'draft'):
if move.picking_id:
pickings[move.picking_id.id] = True
if move.move_dest_id and move.move_dest_id.state == 'waiting':
- self.write(cr, uid, [move.move_dest_id.id], {'state': 'assigned'})
+ self.write(cr, uid, [move.move_dest_id.id], {'state': 'confirmed'})
if context.get('call_unlink',False) and move.move_dest_id.picking_id:
wf_service = netsvc.LocalService("workflow")
wf_service.trg_write(uid, 'stock.picking', move.move_dest_id.picking_id.id, cr)
self.write(cr, uid, ids, {'state': 'cancel', 'move_dest_id': False})
if not context.get('call_unlink',False):
- for pick in self.pool.get('stock.picking').browse(cr, uid, pickings.keys()):
+ for pick in picking_obj.browse(cr, uid, pickings.keys()):
if all(move.state == 'cancel' for move in pick.move_lines):
- self.pool.get('stock.picking').write(cr, uid, [pick.id], {'state': 'cancel'})
+ picking_obj.write(cr, uid, [pick.id], {'state': 'cancel'})
wf_service = netsvc.LocalService("workflow")
for id in ids:
"""
product_obj=self.pool.get('product.product')
accounts = product_obj.get_product_accounts(cr, uid, move.product_id.id, context)
- acc_src = accounts['stock_account_input']
- acc_dest = accounts['stock_account_output']
+ 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']
+
acc_variation = accounts.get('property_stock_variation', False)
journal_id = accounts['stock_journal']
if acc_dest == acc_variation:
- raise osv.except_osv(_('Error!'), _('Can not create Journal Entry, Output Account defined on this product and Variant account on category of this product is same.'))
+ raise osv.except_osv(_('Error!'), _('Can not create Journal Entry, Output Account defined on this product and Variant account on category of this product are same.'))
if acc_src == acc_variation:
- raise osv.except_osv(_('Error!'), _('Can not create Journal Entry, Input Account defined on this product and Variant account on category of this product is same.'))
+ raise osv.except_osv(_('Error!'), _('Can not create Journal Entry, Input Account defined on this product and Variant account on category of this product are same.'))
if not acc_src:
raise osv.except_osv(_('Error!'), _('There is no stock input account defined for this product or its category: "%s" (id: %d)') % \
if not acc_variation:
raise osv.except_osv(_('Error!'), _('There is no inventory variation account defined 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_variation
def _get_reference_accounting_values_for_valuation(self, cr, uid, move, context=None):
todo.append(move.id)
if todo:
self.action_confirm(cr, uid, todo, context=context)
+ todo = []
for move in self.browse(cr, uid, ids, context=context):
if move.state in ['done','cancel']:
self.write(cr, uid, [move.id], {'move_history_ids': [(4, move.move_dest_id.id)]})
#cr.execute('insert into stock_move_history_ids (parent_id,child_id) values (%s,%s)', (move.id, move.move_dest_id.id))
if move.move_dest_id.state in ('waiting', 'confirmed'):
- self.write(cr, uid, [move.move_dest_id.id], {'state': 'assigned'})
+ if move.prodlot_id.id and move.product_id.id == move.move_dest_id.product_id.id:
+ self.write(cr, uid, [move.move_dest_id.id], {'prodlot_id':move.prodlot_id.id})
+ self.force_assign(cr, uid, [move.move_dest_id.id], context=context)
if move.move_dest_id.picking_id:
wf_service.trg_write(uid, 'stock.picking', move.move_dest_id.picking_id.id, cr)
if move.move_dest_id.auto_validate:
prodlot_id = partial_datas and partial_datas.get('move%s_prodlot_id' % (move.id), False)
if prodlot_id:
self.write(cr, uid, [move.id], {'prodlot_id': prodlot_id}, context=context)
+ if move.state not in ('confirmed','done','assigned'):
+ todo.append(move.id)
+
+ if todo:
+ self.action_confirm(cr, uid, todo, context=context)
- self.write(cr, uid, move_ids, {'state': 'done', 'date_planned': time.strftime('%Y-%m-%d %H:%M:%S')}, context=context)
+ self.write(cr, uid, move_ids, {'state': 'done', 'date': time.strftime('%Y-%m-%d %H:%M:%S')}, context=context)
for id in move_ids:
wf_service.trg_trigger(uid, 'stock.move', id, cr)
if quantity <= 0:
raise osv.except_osv(_('Warning!'), _('Please provide a positive quantity to scrap!'))
res = []
+ product_obj = self.pool.get('product.product')
for move in self.browse(cr, uid, ids, context=context):
move_qty = move.product_qty
uos_qty = quantity / move_qty * move.product_uos_qty
'tracking_id': move.tracking_id.id,
'prodlot_id': move.prodlot_id.id,
}
+ if move.location_id.usage <> 'internal':
+ default_val.update({'location_id': move.location_dest_id.id})
new_move = self.copy(cr, uid, move.id, default_val)
res += [new_move]
- product_obj = self.pool.get('product.product')
+
for (id, name) in product_obj.name_get(cr, uid, [move.product_id.id]):
self.log(cr, uid, move.id, "%s x %s %s" % (move.product_qty, name, _("were scrapped")))
if quantity <= 0:
raise osv.except_osv(_('Warning!'), _('Please provide Proper Quantity !'))
res = []
+ product_obj = self.pool.get('product.product')
for move in self.browse(cr, uid, ids, context=context):
move_qty = move.product_qty
+ if move_qty <= 0:
+ raise osv.except_osv(_('Error!'), _('Can not consume a move with negative or zero quantity !'))
quantity_rest = move.product_qty
quantity_rest -= quantity
uos_qty_rest = quantity_rest / move_qty * move.product_uos_qty
}
self.write(cr, uid, [move.id], update_val)
- product_obj = self.pool.get('product.product')
for new_move in self.browse(cr, uid, res, context=context):
for (id, name) in product_obj.name_get(cr, uid, [new_move.product_id.id]):
message = _('Product ') + " '" + name + "' "+ _("is consumed with") + " '" + str(new_move.product_qty) + "' "+ _("quantity.")
defaults.update(prodlot_id=prodlot_id)
new_move = self.copy(cr, uid, move.id, defaults)
complete.append(self.browse(cr, uid, new_move))
- self.write(cr, uid, move.id,
+ self.write(cr, uid, [move.id],
{
'product_qty' : move.product_qty - product_qty,
'product_uos_qty':move.product_qty - product_qty,
for move in too_many:
- self.write(cr, uid, move.id,
+ self.write(cr, uid, [move.id],
{
'product_qty': move.product_qty,
'product_uos_qty': move.product_qty,
}
_defaults = {
- 'date': time.strftime('%Y-%m-%d %H:%M:%S'),
+ 'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
'state': 'draft',
'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.inventory', context=c)
}
return self.pool.get('stock.move').create(cr, uid, move_vals)
def action_done(self, cr, uid, ids, context=None):
- """ Finished the inventory
+ """ Finish the inventory
@return: True
"""
-
if context is None:
context = {}
move_obj = self.pool.get('stock.move')
move_ids = []
for line in inv.inventory_line_id:
pid = line.product_id.id
- product_context.update(uom=line.product_uom.id)
+ product_context.update(uom=line.product_uom.id, date=inv.date, prodlot_id=line.prod_lot_id.id)
amount = location_obj._product_get(cr, uid, line.location_id.id, [pid], product_context)[pid]
+
change = line.product_qty - amount
lot_id = line.prod_lot_id.id
if change:
'product_uom': line.product_uom.id,
'prodlot_id': lot_id,
'date': inv.date,
- 'date': inv.date,
}
if change > 0:
value.update( {
'location_id': line.location_id.id,
'location_dest_id': location_id,
})
- if lot_id:
- value.update({
- 'prodlot_id': lot_id,
- 'product_qty': line.product_qty
- })
move_ids.append(self._inventory_line_hook(cr, uid, line, value))
message = _('Inventory') + " '" + inv.name + "' "+ _("is done.")
self.log(cr, uid, inv.id, message)
self.write(cr, uid, [inv.id], {'state': 'confirm', 'move_ids': [(6, 0, move_ids)]})
return True
- def action_cancel(self, cr, uid, ids, context=None):
+ def action_cancel_draft(self, cr, uid, ids, context=None):
""" Cancels the stock move and change inventory state to draft.
@return: True
"""
for inv in self.browse(cr, uid, ids, context=context):
- self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in inv.move_ids], context)
- self.write(cr, uid, [inv.id], {'state': 'draft'})
+ self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in inv.move_ids], context=context)
+ self.write(cr, uid, [inv.id], {'state':'draft'}, context=context)
return True
def action_cancel_inventary(self, cr, uid, ids, context=None):
""" Cancels both stock move and inventory
@return: True
"""
+ move_obj = self.pool.get('stock.move')
+ account_move_obj = self.pool.get('account.move')
for inv in self.browse(cr, uid, ids, context=context):
- self.pool.get('stock.move').action_cancel(cr, uid, [x.id for x in inv.move_ids], context)
- self.write(cr, uid, [inv.id], {'state':'cancel'})
+ move_obj.action_cancel(cr, uid, [x.id for x in inv.move_ids], context=context)
+ for move in inv.move_ids:
+ account_move_ids = account_move_obj.search(cr, uid, [('name', '=', move.name)])
+ if account_move_ids:
+ account_move_data_l = account_move_obj.read(cr, uid, account_move_ids, ['state'], context=context)
+ for account_move in account_move_data_l:
+ if account_move['state'] == 'posted':
+ raise osv.except_osv(_('UserError'),
+ _('You can not cancel inventory which has any account move with posted state.'))
+ account_move_obj.unlink(cr, uid, [account_move['id']], context=context)
+ self.write(cr, uid, [inv.id], {'state': 'cancel'}, context=context)
return True
stock_inventory()
'product_id': fields.many2one('product.product', 'Product', required=True, select=True),
'product_uom': fields.many2one('product.uom', 'Product UOM', required=True),
'product_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product UoM')),
- 'company_id': fields.related('inventory_id','company_id',type='many2one',relation='res.company',string='Company',store=True, select=True),
+ 'company_id': fields.related('inventory_id','company_id',type='many2one',relation='res.company',string='Company',store=True, select=True, readonly=True),
'prod_lot_id': fields.many2one('stock.production.lot', 'Production Lot', domain="[('product_id','=',product_id)]"),
'state': fields.related('inventory_id','state',type='char',string='State',readonly=True),
}
- def on_change_product_id(self, cr, uid, ids, location_id, product, uom=False):
+ def on_change_product_id(self, cr, uid, ids, location_id, product, uom=False, to_date=False):
""" Changes UoM and name if product_id changes.
@param location_id: Location id
@param product: Changed product_id
@return: Dictionary of changed values
"""
if not product:
- return {}
- if not uom:
- prod = self.pool.get('product.product').browse(cr, uid, [product], {'uom': uom})[0]
- uom = prod.uom_id.id
- amount = self.pool.get('stock.location')._product_get(cr, uid, location_id, [product], {'uom': uom})[product]
+ return {'value': {'product_qty': 0.0, 'product_uom': False}}
+ obj_product = self.pool.get('product.product').browse(cr, uid, product)
+ uom = uom or obj_product.uom_id.id
+ amount = self.pool.get('stock.location')._product_get(cr, uid, location_id, [product], {'uom': uom, 'to_date': to_date})[product]
result = {'product_qty': amount, 'product_uom': uom}
return {'value': result}