'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()
_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:
""" 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
@return {'contact': address, 'invoice': address} for invoice
"""
partner_obj = self.pool.get('res.partner')
- partner = (picking.purchase_id and picking.purchase_id.partner_id) or (picking.sale_id and picking.sale_id.partner_id) or picking.address_id.partner_id
-
+ 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
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})
{'price_unit': product_price,
'price_currency_id': product_currency})
- if not move.returned_price:
- move_obj.write(cr, uid, [move.id], {'returned_price': move.price_unit})
for move in too_few:
product_qty = move_product_qty[move.id]
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 = {
'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)]}),
# used for colors in tree views:
'scrapped': fields.related('location_dest_id','scrap_location',type='boolean',relation='stock.location',string='Scrapped', readonly=True),
- 'returned_price': fields.float('Returned product price', digits_compute= dp.get_precision('Sale Price')),
}
_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):
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 = {}
- 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)
- self.pool.get('stock.picking').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:
- 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 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 = []
for move in self.browse(cr, uid, ids, context=context):
- #print 'DONE MOVE', move.id, move.product_id.name, move.move_dest_id.id, move.state, move.move_dest_id and move.move_dest_id.state
if move.state=="draft":
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'):
+ 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)
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:
}
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)
}
move_ids = []
for line in inv.inventory_line_id:
pid = line.product_id.id
- product_context.update(uom=line.product_uom.id,date=inv.date)
+ 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
'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=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):
- move_obj.action_cancel(cr, uid, [x.id for x in inv.move_ids], context)
+ 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)])
+ 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'])
+ 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']])
- self.write(cr, uid, [inv.id], {'state': 'draft'})
- return True
-
- def action_cancel_inventary(self, cr, uid, ids, context=None):
- """ Cancels both stock move and inventory
- @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':'cancel'})
+ 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),
}
@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
+ 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}