from dateutil import relativedelta
import json
import time
+import sets
+import openerp
from openerp.osv import fields, osv
from openerp.tools.float_utils import float_compare, float_round
from openerp.tools.translate import _
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT, DEFAULT_SERVER_DATE_FORMAT
-from openerp import SUPERUSER_ID, api
+from openerp import SUPERUSER_ID, api, models
import openerp.addons.decimal_precision as dp
from openerp.addons.procurement import procurement
import logging
\n* Production: Virtual counterpart location for production operations: this location consumes the raw material and produces finished products
\n* Transit Location: Counterpart location that should be used in inter-companies or inter-warehouses operations
""", select=True),
- 'complete_name': fields.function(_complete_name, type='char', string="Location Name",
+ 'complete_name': fields.function(_complete_name, type='char', string="Full Location Name",
store={'stock.location': (_get_sublocations, ['name', 'location_id', 'active'], 10)}),
'location_id': fields.many2one('stock.location', 'Parent Location', select=True, ondelete='cascade'),
'child_ids': fields.one2many('stock.location', 'location_id', 'Contains'),
'scrap_location': fields.boolean('Is a Scrap Location?', help='Check this box to allow using this location to put scrapped/damaged goods.'),
'removal_strategy_id': fields.many2one('product.removal', 'Removal Strategy', help="Defines the default method used for suggesting the exact location (shelf) where to take the products from, which lot etc. for this location. This method can be enforced at the product category level, and a fallback is made on the parent locations if none is set here."),
'putaway_strategy_id': fields.many2one('product.putaway', 'Put Away Strategy', help="Defines the default method used for suggesting the exact location (shelf) where to store the products. This method can be enforced at the product category level, and a fallback is made on the parent locations if none is set here."),
- 'loc_barcode': fields.char('Location Barcode'),
+ 'barcode': fields.char('Barcode', oldname='loc_barcode'),
}
_defaults = {
'active': True,
'posz': 0,
'scrap_location': False,
}
- _sql_constraints = [('loc_barcode_company_uniq', 'unique (loc_barcode,company_id)', 'The barcode for a location must be unique per company !')]
+ _sql_constraints = [('barcode_company_uniq', 'unique (barcode,company_id)', 'The barcode for a location must be unique per company !')]
def create(self, cr, uid, default, context=None):
- if not default.get('loc_barcode', False):
- default.update({'loc_barcode': default.get('complete_name', False)})
+ if not default.get('barcode', False):
+ default.update({'barcode': default.get('complete_name', False)})
return super(stock_location, self).create(cr, uid, default, context=context)
def get_putaway_strategy(self, cr, uid, location, product, context=None):
quant = self._quant_create(cr, uid, qty, move, lot_id=lot_id, owner_id=owner_id, src_package_id=src_package_id, dest_package_id=dest_package_id, force_location_from=location_from, force_location_to=location_to, context=context)
else:
self._quant_split(cr, uid, quant, qty, context=context)
- quant.refresh()
to_move_quants.append(quant)
quants_reconcile.append(quant)
if to_move_quants:
if location_to.usage == 'internal':
if self.search(cr, uid, [('product_id', '=', move.product_id.id), ('qty','<', 0)], limit=1, context=context):
for quant in quants_reconcile:
- quant.refresh()
self._quant_reconcile_negative(cr, uid, quant, move, context=context)
def move_quants_write(self, cr, uid, quants, move, location_dest_id, dest_package_id, context=None):
def _quant_split(self, cr, uid, quant, qty, context=None):
context = context or {}
rounding = quant.product_id.uom_id.rounding
- if (quant.qty > 0 and float_compare(quant.qty, qty, precision_rounding=rounding) <= 0)\
- or (quant.qty <= 0 and float_compare(quant.qty, qty, precision_rounding=rounding) >= 0) :
- #(quant.qty > 0 and quant.qty <= qty) or (quant.qty <= 0 and quant.qty >= qty):
+ if float_compare(abs(quant.qty), abs(qty), precision_rounding=rounding) <= 0: # if quant <= qty in abs, take it entirely
return False
qty_round = float_round(qty, precision_rounding=rounding)
new_qty_round = float_round(quant.qty - qty, precision_rounding=rounding)
new_quant = self.copy(cr, SUPERUSER_ID, quant.id, default={'qty': new_qty_round, 'history_ids': [(4, x.id) for x in quant.history_ids]}, context=context)
self.write(cr, SUPERUSER_ID, quant.id, {'qty': qty_round}, context=context)
- quant.refresh()
return self.browse(cr, uid, new_quant, context=context)
def _get_latest_move(self, cr, uid, quant, context=None):
remaining_to_solve_quant_ids = self.search(cr, uid, [('propagated_from_id', '=', quant_neg.id), ('id', 'not in', solved_quant_ids)], context=context)
if remaining_to_solve_quant_ids:
self.write(cr, SUPERUSER_ID, remaining_to_solve_quant_ids, {'propagated_from_id': remaining_neg_quant.id}, context=context)
- if solving_quant.propagated_from_id:
- self.write(cr, uid, solved_quant_ids, {'propagated_from_id': solving_quant.propagated_from_id.id})
+ if solving_quant.propagated_from_id and solved_quant_ids:
+ self.write(cr, uid, solved_quant_ids, {'propagated_from_id': solving_quant.propagated_from_id.id}, context=context)
#delete the reconciled quants, as it is replaced by the solved quants
self.unlink(cr, SUPERUSER_ID, [quant_neg.id], context=context)
- #price update + accounting entries adjustments
- self._price_update(cr, uid, solved_quant_ids, solving_quant.cost, context=context)
- #merge history (and cost?)
- self._quants_merge(cr, uid, solved_quant_ids, solving_quant, context=context)
+ if solved_quant_ids:
+ #price update + accounting entries adjustments
+ self._price_update(cr, uid, solved_quant_ids, solving_quant.cost, context=context)
+ #merge history (and cost?)
+ self._quants_merge(cr, uid, solved_quant_ids, solving_quant, context=context)
self.unlink(cr, SUPERUSER_ID, [solving_quant.id], context=context)
solving_quant = remaining_solving_quant
res.append((None, quantity))
break
for quant in self.browse(cr, uid, quants, context=context):
- qty_cmp = float_compare(quantity, abs(quant.qty), precision_rounding=product.uom_id.rounding)
- qty0_cmp = float_compare(quantity, 0.0, precision_rounding=product.uom_id.rounding)
- if qty_cmp >= 0:
+ rounding = product.uom_id.rounding
+ if float_compare(quantity, abs(quant.qty), precision_rounding=rounding) >= 0:
res += [(quant, abs(quant.qty))]
quantity -= abs(quant.qty)
- elif qty0_cmp != 0:
+ elif float_compare(quantity, 0.0, precision_rounding=rounding) != 0:
res += [(quant, quantity)]
quantity = 0
break
class stock_picking(osv.osv):
_name = "stock.picking"
_inherit = ['mail.thread']
- _description = "Picking List"
+ _description = "Transfer"
_order = "priority desc, date asc, id desc"
def _set_min_date(self, cr, uid, id, field, value, arg, context=None):
if ('name' not in vals) or (vals.get('name') in ('/', False)):
ptype_id = vals.get('picking_type_id', context.get('default_picking_type_id', False))
sequence_id = self.pool.get('stock.picking.type').browse(cr, user, ptype_id, context=context).sequence_id.id
- vals['name'] = self.pool.get('ir.sequence').get_id(cr, user, sequence_id, 'id', context=context)
+ vals['name'] = self.pool.get('ir.sequence').next_by_id(cr, user, sequence_id, context=context)
return super(stock_picking, self).create(cr, user, vals, context)
def _state_get(self, cr, uid, ids, field_name, arg, context=None):
for pick in self.browse(cr, uid, ids, context=context):
if pick.state == 'draft':
self.action_confirm(cr, uid, [pick.id], context=context)
- pick.refresh()
#skip the moves that don't need to be checked
move_ids = [x.id for x in pick.move_lines if x.state not in ('draft', 'cancel', 'done')]
if not move_ids:
product_putaway_strats[product.id] = location
return location or picking.location_dest_id.id
+ # If we encounter an UoM that is smaller than the default UoM or the one already chosen, use the new one instead.
+ product_uom = {} # Determines UoM used in pack operations
+ for move in picking.move_lines:
+ if not product_uom.get(move.product_id.id):
+ product_uom[move.product_id.id] = move.product_id.uom_id.id
+ if move.product_uom.id != move.product_id.uom_id.id and move.product_uom.factor > product_uom[move.product_id.id]:
+ product_uom[move.product_id.id] = move.product_uom.id
+
pack_obj = self.pool.get("stock.quant.package")
quant_obj = self.pool.get("stock.quant")
vals = []
qtys_grouped[key] = qty
# Create the necessary operations for the grouped quants and remaining qtys
+ uom_obj = self.pool.get('product.uom')
for key, qty in qtys_grouped.items():
+ product = self.pool.get("product.product").browse(cr, uid, key[0], context=context)
+ uom_id = product.uom_id.id
+ qty_uom = qty
+ if product_uom.get(key[0]):
+ uom_id = product_uom[key[0]]
+ qty_uom = uom_obj._compute_qty(cr, uid, product.uom_id.id, qty, uom_id)
vals.append({
'picking_id': picking.id,
- 'product_qty': qty,
+ 'product_qty': qty_uom,
'product_id': key[0],
'package_id': key[1],
'lot_id': key[2],
'owner_id': key[3],
'location_id': key[4],
'location_dest_id': key[5],
- 'product_uom_id': self.pool.get("product.product").browse(cr, uid, key[0], context=context).uom_id.id,
+ 'product_uom_id': uom_id,
})
return vals
@api.cr_uid_ids_context
def open_barcode_interface(self, cr, uid, picking_ids, context=None):
- final_url="/barcode/web/#action=stock.ui&picking_id="+str(picking_ids[0])
+ final_url="/stock/barcode/#action=stock.ui&picking_id="+str(picking_ids[0])
return {'type': 'ir.actions.act_url', 'url':final_url, 'target': 'self',}
@api.cr_uid_ids_context
uom_obj = self.pool.get('product.uom')
package_obj = self.pool.get('stock.quant.package')
quant_obj = self.pool.get('stock.quant')
+ link_obj = self.pool.get('stock.move.operation.link')
quants_in_package_done = set()
prod2move_ids = {}
still_to_do = []
operations = picking.pack_operation_ids
operations = sorted(operations, key=lambda x: ((x.package_id and not x.product_id) and -4 or 0) + (x.package_id and -2 or 0) + (x.lot_id and -1 or 0))
#delete existing operations to start again from scratch
- cr.execute("DELETE FROM stock_move_operation_link WHERE operation_id in %s", (tuple([x.id for x in operations]),))
+ links = link_obj.search(cr, uid, [('operation_id', 'in', [x.id for x in operations])], context=context)
+ if links:
+ link_obj.unlink(cr, uid, links, context=context)
#1) first, try to create links when quants can be identified without any doubt
for ops in operations:
#for each operation, create the links with the stock move by seeking on the matching reserved quants,
need_rereserve = True
elif ops.product_id.id:
#Check moves with same product
- qty_to_assign = uom_obj._compute_qty_obj(cr, uid, ops.product_uom_id, ops.product_qty, ops.product_id.uom_id, round=False, context=context)
+ qty_to_assign = uom_obj._compute_qty_obj(cr, uid, ops.product_uom_id, ops.product_qty, ops.product_id.uom_id, context=context)
for move_dict in prod2move_ids.get(ops.product_id.id, []):
move = move_dict['move']
for quant in move.reserved_quant_ids:
"""
Creates an extra move when there is no corresponding original move to be copied
"""
+ uom_obj = self.pool.get("product.uom")
+ uom_id = product.uom_id.id
+ qty = remaining_qty
+ if op.product_id and op.product_uom_id and op.product_uom_id.id != product.uom_id.id:
+ if op.product_uom_id.factor > product.uom_id.factor: #If the pack operation's is a smaller unit
+ uom_id = op.product_uom_id.id
+ #HALF-UP rounding as only rounding errors will be because of propagation of error from default UoM
+ qty = uom_obj._compute_qty_obj(cr, uid, product.uom_id, remaining_qty, op.product_uom_id, rounding_method='HALF-UP')
picking = op.picking_id
res = {
'picking_id': picking.id,
'location_id': picking.location_id.id,
'location_dest_id': picking.location_dest_id.id,
'product_id': product.id,
- 'product_uom': product.uom_id.id,
- 'product_uom_qty': remaining_qty,
+ 'product_uom': uom_id,
+ 'product_uom_qty': qty,
'name': _('Extra Move: ') + product.name,
'state': 'draft',
}
moves = []
for op in picking.pack_operation_ids:
for product_id, remaining_qty in operation_obj._get_remaining_prod_quantities(cr, uid, op, context=context).items():
- if remaining_qty > 0:
- product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
+ product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
+ if float_compare(remaining_qty, 0, precision_rounding=product.uom_id.rounding) > 0:
vals = self._prepare_values_extra_move(cr, uid, op, product, remaining_qty, context=context)
moves.append(move_obj.create(cr, uid, vals, context=context))
if moves:
if not all_op_processed:
todo_move_ids += self._create_extra_moves(cr, uid, picking, context=context)
- picking.refresh()
- #split move lines eventually
-
+ #split move lines if needed
toassign_move_ids = []
for move in picking.move_lines:
remaining_qty = move.remaining_qty
self.pool.get('stock.move').action_done(cr, uid, todo_move_ids, context=context)
elif context.get('do_only_split'):
context = dict(context, split=todo_move_ids)
- picking.refresh()
self._create_backorder(cr, uid, picking, context=context)
if toassign_move_ids:
stock_move_obj.action_assign(cr, uid, toassign_move_ids, context=context)
stock_operation_obj.write(cr, uid, pack_operation_ids, {'result_package_id': package_id}, context=context)
return True
- def process_product_id_from_ui(self, cr, uid, picking_id, product_id, op_id, increment=True, context=None):
+ def process_product_id_from_ui(self, cr, uid, picking_id, product_id, op_id, increment=1, context=None):
return self.pool.get('stock.pack.operation')._search_and_increment(cr, uid, picking_id, [('product_id', '=', product_id),('id', '=', op_id)], increment=increment, context=context)
def process_barcode_from_ui(self, cr, uid, picking_id, barcode_str, visible_op_ids, context=None):
'''This function is called each time there barcode scanner reads an input'''
- lot_obj = self.pool.get('stock.production.lot')
- package_obj = self.pool.get('stock.quant.package')
- product_obj = self.pool.get('product.product')
stock_operation_obj = self.pool.get('stock.pack.operation')
- stock_location_obj = self.pool.get('stock.location')
answer = {'filter_loc': False, 'operation_id': False}
- #check if the barcode correspond to a location
- matching_location_ids = stock_location_obj.search(cr, uid, [('loc_barcode', '=', barcode_str)], context=context)
- if matching_location_ids:
- #if we have a location, return immediatly with the location name
- location = stock_location_obj.browse(cr, uid, matching_location_ids[0], context=None)
- answer['filter_loc'] = stock_location_obj._name_get(cr, uid, location, context=None)
- answer['filter_loc_id'] = matching_location_ids[0]
- return answer
- #check if the barcode correspond to a product
- matching_product_ids = product_obj.search(cr, uid, ['|', ('ean13', '=', barcode_str), ('default_code', '=', barcode_str)], context=context)
- if matching_product_ids:
- op_id = stock_operation_obj._search_and_increment(cr, uid, picking_id, [('product_id', '=', matching_product_ids[0])], filter_visible=True, visible_op_ids=visible_op_ids, increment=True, context=context)
- answer['operation_id'] = op_id
- return answer
+
+ # Barcode Nomenclatures
+ picking_type_id = self.browse(cr, uid, [picking_id], context=context).picking_type_id.id
+ barcode_nom = self.pool.get('stock.picking.type').browse(cr, uid, [picking_type_id], context=context).barcode_nomenclature_id
+ parsed_result = barcode_nom.parse_barcode(barcode_str)
+
+ #check if the barcode is a weighted barcode or simply a product
+ if parsed_result['type'] in ['weight', 'product', 'package']:
+ weight=1
+ if parsed_result['type'] == 'weight':
+ domain = ['|', ('barcode', '=', parsed_result['base_code']), ('default_code', '=', parsed_result['base_code'])]
+ weight=parsed_result['value']
+ obj = self.pool.get('product.product')
+ id_in_operation = 'product_id'
+ elif parsed_result['type'] == 'product':
+ domain = ['|', ('barcode', '=', parsed_result['code']), ('default_code', '=', parsed_result['code'])]
+ obj = self.pool.get('product.product')
+ id_in_operation = 'product_id'
+ else:
+ domain = [('name', '=', parsed_result['code'])]
+ obj = self.pool.get('stock.quant.package')
+ id_in_operation = 'package_id'
+
+ matching_product_ids = obj.search(cr, uid, domain, context=context)
+ if matching_product_ids:
+ op_id = stock_operation_obj._search_and_increment(cr, uid, picking_id, [(id_in_operation, '=', matching_product_ids[0])], filter_visible=True, visible_op_ids=visible_op_ids, increment=weight, context=context)
+ answer['operation_id'] = op_id
+ return answer
+
#check if the barcode correspond to a lot
- matching_lot_ids = lot_obj.search(cr, uid, [('name', '=', barcode_str)], context=context)
- if matching_lot_ids:
- lot = lot_obj.browse(cr, uid, matching_lot_ids[0], context=context)
- op_id = stock_operation_obj._search_and_increment(cr, uid, picking_id, [('product_id', '=', lot.product_id.id), ('lot_id', '=', lot.id)], filter_visible=True, visible_op_ids=visible_op_ids, increment=True, context=context)
- answer['operation_id'] = op_id
- return answer
- #check if the barcode correspond to a package
- matching_package_ids = package_obj.search(cr, uid, [('name', '=', barcode_str)], context=context)
- if matching_package_ids:
- op_id = stock_operation_obj._search_and_increment(cr, uid, picking_id, [('package_id', '=', matching_package_ids[0])], filter_visible=True, visible_op_ids=visible_op_ids, increment=True, context=context)
- answer['operation_id'] = op_id
- return answer
+ elif parsed_result['type'] == 'lot':
+ lot_obj = self.pool.get('stock.production.lot')
+ matching_lot_ids = lot_obj.search(cr, uid, [('name', '=', parsed_result['code'])], context=context)
+ if matching_lot_ids:
+ lot = lot_obj.browse(cr, uid, matching_lot_ids[0], context=context)
+ op_id = stock_operation_obj._search_and_increment(cr, uid, picking_id, [('product_id', '=', lot.product_id.id), ('lot_id', '=', lot.id)], filter_visible=True, visible_op_ids=visible_op_ids, increment=1, context=context)
+ answer['operation_id'] = op_id
+ return answer
+
+ #check if the barcode correspond to a location
+ elif parsed_result['type'] == 'location':
+ stock_location_obj = self.pool.get('stock.location')
+ matching_location_ids = stock_location_obj.search(cr, uid, [('barcode', '=', parsed_result['code'])], context=context)
+ if matching_location_ids:
+ #if we have a location, return immediatly with the location name
+ location = stock_location_obj.browse(cr, uid, matching_location_ids[0], context=None)
+ answer['filter_loc'] = stock_location_obj._name_get(cr, uid, location, context=None)
+ answer['filter_loc_id'] = matching_location_ids[0]
+ return answer
+
return answer
'create_date': fields.datetime('Creation Date'),
}
_defaults = {
- 'name': lambda x, y, z, c: x.pool.get('ir.sequence').get(y, z, 'stock.lot.serial'),
+ 'name': lambda x, y, z, c: x.pool.get('ir.sequence').next_by_code(y, z, 'stock.lot.serial'),
'product_id': lambda x, y, z, c: c.get('product_id', False),
}
_sql_constraints = [
for record in move.linked_move_operation_ids:
qty -= record.qty
# Keeping in product default UoM
- res[move.id] = qty
+ res[move.id] = float_round(qty, precision_rounding=move.product_id.uom_id.rounding)
return res
def _get_lot_ids(self, cr, uid, ids, field_name, args, context=None):
'move_dest_id': fields.many2one('stock.move', 'Destination Move', help="Optional: next stock move when chaining them", select=True, copy=False),
'move_orig_ids': fields.one2many('stock.move', 'move_dest_id', 'Original Move', help="Optional: previous stock move when chaining them", select=True),
- 'picking_id': fields.many2one('stock.picking', 'Reference', select=True, states={'done': [('readonly', True)]}),
+ 'picking_id': fields.many2one('stock.picking', 'Transfer Reference', select=True, states={'done': [('readonly', True)]}),
'note': fields.text('Notes'),
'state': fields.selection([('draft', 'New'),
('cancel', 'Cancelled'),
'company_id': fields.many2one('res.company', 'Company', required=True, select=True),
'split_from': fields.many2one('stock.move', string="Move Split From", help="Technical field used to track the origin of a split move, which can be useful in case of debug", copy=False),
'backorder_id': fields.related('picking_id', 'backorder_id', type='many2one', relation="stock.picking", string="Back Order of", select=True),
- 'origin': fields.char("Source"),
+ 'origin': fields.char("Source Document"),
'procure_method': fields.selection([('make_to_stock', 'Default: Take From Stock'), ('make_to_order', 'Advanced: Apply Procurement Rules')], 'Supply Method', required=True,
help="""By default, the system will take from the stock in the source location and passively wait for availability. The other possibility allows you to directly create a procurement on the source location (and thus ignore its current stock) to gather products. If we want to chain moves and have this one to wait for the previous, this second option should be chosen."""),
'quant_ids': fields.many2many('stock.quant', 'stock_quant_move_rel', 'move_id', 'quant_id', 'Moved Quants'),
'reserved_quant_ids': fields.one2many('stock.quant', 'reservation_id', 'Reserved quants'),
'linked_move_operation_ids': fields.one2many('stock.move.operation.link', 'move_id', string='Linked Operations', readonly=True, help='Operations that impact this move for the computation of the remaining quantities'),
- 'remaining_qty': fields.function(_get_remaining_qty, type='float', string='Remaining Quantity',
+ 'remaining_qty': fields.function(_get_remaining_qty, type='float', string='Remaining Quantity', digits=0,
states={'done': [('readonly', True)]}, help="Remaining Quantity in default UoM according to operations matched with this move"),
'procurement_id': fields.many2one('procurement.order', 'Procurement'),
'group_id': fields.many2one('procurement.group', 'Procurement Group'),
'company_id': move.company_id and move.company_id.id or False,
'date_planned': move.date,
'product_id': move.product_id.id,
- 'product_qty': move.product_qty,
+ 'product_qty': move.product_uom_qty,
'product_uom': move.product_uom.id,
- 'product_uos_qty': (move.product_uos and move.product_uos_qty) or move.product_qty,
+ 'product_uos_qty': (move.product_uos and move.product_uos_qty) or move.product_uom_qty,
'product_uos': (move.product_uos and move.product_uos.id) or move.product_uom.id,
'location_id': move.location_id.id,
'move_dest_id': move.id,
for move in todo_moves:
if move.linked_move_operation_ids:
continue
- move.refresh()
#then if the move isn't totally assigned, try to find quants without any specific domain
if move.state != 'assigned':
qty_already_assigned = move.reserved_availability
# Handle pack in pack
if not ops.product_id and ops.package_id and ops.result_package_id.id != ops.package_id.parent_id.id:
self.pool.get('stock.quant.package').write(cr, SUPERUSER_ID, [ops.package_id.id], {'parent_id': ops.result_package_id.id}, context=context)
+ if not move_qty.get(move.id):
+ raise osv.except_osv(_("Error"), _("The roundings of your Unit of Measures %s on the move vs. %s on the product don't allow to do these operations or you are not transferring the picking at once. ") % (move.product_uom.name, move.product_id.uom_id.name))
move_qty[move.id] -= record.qty
#Check for remaining qtys and unreserve/check move_dest_id in
move_dest_ids = set()
for move in self.browse(cr, uid, ids, context=context):
- if move_qty[move.id] > 0: # (=In case no pack operations in picking)
+ move_qty_cmp = float_compare(move_qty[move.id], 0, precision_rounding=move.product_id.uom_id.rounding)
+ if move_qty_cmp > 0: # (=In case no pack operations in picking)
main_domain = [('qty', '>', 0)]
prefered_domain = [('reservation_id', '=', move.id)]
fallback_domain = [('reservation_id', '=', False)]
uom_obj = self.pool.get('product.uom')
context = context or {}
- uom_qty = uom_obj._compute_qty_obj(cr, uid, move.product_id.uom_id, qty, move.product_uom)
+ #HALF-UP rounding as only rounding errors will be because of propagation of error from default UoM
+ uom_qty = uom_obj._compute_qty_obj(cr, uid, move.product_id.uom_id, qty, move.product_uom, rounding_method='HALF-UP', context=context)
uos_qty = uom_qty * move.product_uos_qty / move.product_uom_qty
defaults = {
if inventory_line.product_qty < 0 and inventory_line.product_qty != inventory_line.theoretical_qty:
raise osv.except_osv(_('Warning'), _('You cannot set a negative product quantity in an inventory line:\n\t%s - qty: %s' % (inventory_line.product_id.name, inventory_line.product_qty)))
self.action_check(cr, uid, [inv.id], context=context)
- inv.refresh()
self.write(cr, uid, [inv.id], {'state': 'done'}, context=context)
self.post_inventory(cr, uid, inv, context=context)
return True
'product_qty': 1,
}
+ def create(self, cr, uid, values, context=None):
+ if context is None:
+ context = {}
+ product_obj = self.pool.get('product.product')
+ if 'product_id' in values and not 'product_uom_id' in values:
+ values['product_uom_id'] = product_obj.browse(cr, uid, values.get('product_id'), context=context).uom_id.id
+ return super(stock_inventory_line, self).create(cr, uid, values, context=context)
+
def _resolve_inventory_line(self, cr, uid, inventory_line, context=None):
stock_move_obj = self.pool.get('stock.move')
diff = inventory_line.theoretical_qty - inventory_line.product_qty
new_id = super(stock_warehouse, self).create(cr, uid, vals=vals, context=context)
warehouse = self.browse(cr, uid, new_id, context=context)
self.create_sequences_and_picking_types(cr, uid, warehouse, context=context)
- warehouse.refresh()
#create routes and push/pull rules
new_objects_dict = self.create_routes(cr, uid, new_id, warehouse, context=context)
self.change_route(cr, uid, ids, warehouse, vals.get('reception_steps', False), vals.get('delivery_steps', False), context=context_with_inactive)
# Check if we need to change something to resupply warehouses and associated MTO rules
self._check_resupply(cr, uid, warehouse, vals.get('reception_steps'), vals.get('delivery_steps'), context=context)
- warehouse.refresh()
if vals.get('code') or vals.get('name'):
name = warehouse.name
#rename sequence
'date_expected': newdate,
'location_dest_id': rule.location_dest_id.id
})
- move.refresh()
#avoid looping if a push rule is not well configured
if rule.location_dest_id.id != old_dest_location:
#call again push_apply to see if a next step is defined
}, readonly=True, select=True),
}
_defaults = {
- 'name': lambda self, cr, uid, context: self.pool.get('ir.sequence').get(cr, uid, 'stock.quant.package') or _('Unknown Pack')
+ 'name': lambda self, cr, uid, context: self.pool.get('ir.sequence').next_by_code(cr, uid, 'stock.quant.package') or _('Unknown Pack')
}
def _check_location_constraint(self, cr, uid, packs, context=None):
qty = uom_obj._compute_qty_obj(cr, uid, ops.product_uom_id, ops.product_qty, ops.product_id.uom_id, context=context)
for record in ops.linked_move_operation_ids:
qty -= record.qty
- res[ops.id] = qty
+ res[ops.id] = float_round(qty, precision_rounding=ops.product_id.uom_id.rounding)
return res
def product_id_change(self, cr, uid, ids, product_id, product_uom_id, product_qty, context=None):
'cost': fields.float("Cost", help="Unit Cost for this product line"),
'currency': fields.many2one('res.currency', string="Currency", help="Currency in which Unit cost is expressed", ondelete='CASCADE'),
'linked_move_operation_ids': fields.one2many('stock.move.operation.link', 'operation_id', string='Linked Moves', readonly=True, help='Moves impacted by this operation for the computation of the remaining quantities'),
- 'remaining_qty': fields.function(_get_remaining_qty, type='float', string="Remaining Qty", help="Remaining quantity in default UoM according to moves matched with this operation. "),
+ 'remaining_qty': fields.function(_get_remaining_qty, type='float', digits = 0, string="Remaining Qty", help="Remaining quantity in default UoM according to moves matched with this operation. "),
'location_id': fields.many2one('stock.location', 'Source Location', required=True),
'location_dest_id': fields.many2one('stock.location', 'Destination Location', required=True),
'processed': fields.selection([('true','Yes'), ('false','No')],'Has been processed?', required=True),
new_lot_id = self.pool.get('stock.production.lot').create(cr, uid, val, context=context)
self.write(cr, uid, id, {'lot_id': new_lot_id}, context=context)
- def _search_and_increment(self, cr, uid, picking_id, domain, filter_visible=False, visible_op_ids=False, increment=True, context=None):
- '''Search for an operation with given 'domain' in a picking, if it exists increment the qty (+1) otherwise create it
+ def _search_and_increment(self, cr, uid, picking_id, domain, filter_visible=False, visible_op_ids=False, increment=1, context=None):
+ '''Search for an operation with given 'domain' in a picking, if it exists increment the qty by the value of increment otherwise create it
:param domain: list of tuple directly reusable as a domain
context can receive a key 'current_package_id' with the package to consider for this operation
operation_id = todo_operation_ids[0]
op_obj = self.browse(cr, uid, operation_id, context=context)
qty = op_obj.qty_done
- if increment:
- qty += 1
- else:
- qty -= 1 if qty >= 1 else 0
+ if increment > 0:
+ qty += increment
+ elif increment < 0:
if qty == 0 and op_obj.product_qty == 0:
#we have a line with 0 qty set, so delete it
self.unlink(cr, uid, [operation_id], context=context)
return False
+ else:
+ qty = max(0, qty-1)
self.write(cr, uid, [operation_id], {'qty_done': qty}, context=context)
else:
#no existing operation found for the given domain and picking => create a new one
'product_qty': 0,
'location_id': picking.location_id.id,
'location_dest_id': picking.location_dest_id.id,
- 'qty_done': 1,
+ 'qty_done': increment,
}
for key in domain:
var_name, dummy, value = key
'active': lambda *a: 1,
'logic': lambda *a: 'max',
'qty_multiple': lambda *a: 1,
- 'name': lambda self, cr, uid, context: self.pool.get('ir.sequence').get(cr, uid, 'stock.orderpoint') or '',
+ 'name': lambda self, cr, uid, context: self.pool.get('ir.sequence').next_by_code(cr, uid, 'stock.orderpoint') or '',
'product_uom': lambda self, cr, uid, context: context.get('product_uom', False),
'company_id': lambda self, cr, uid, context: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.warehouse.orderpoint', context=context)
}
_order = 'sequence'
def open_barcode_interface(self, cr, uid, ids, context=None):
- final_url = "/barcode/web/#action=stock.ui&picking_type_id=" + str(ids[0]) if len(ids) else '0'
+ final_url = "/stock/barcode/#action=stock.ui&picking_type_id=" + str(ids[0]) if len(ids) else '0'
return {'type': 'ir.actions.act_url', 'url': final_url, 'target': 'self'}
def _get_tristate_values(self, cr, uid, ids, field_name, arg, context=None):
'rate_picking_backorders': fields.function(_get_picking_count,
type='integer', multi='_get_picking_count'),
+ # Barcode nomenclature
+ 'barcode_nomenclature_id': fields.many2one('barcode.nomenclature','Barcode Nomenclature', help='A barcode nomenclature', required=True),
}
+
+ def _get_default_nomenclature(self, cr, uid, context=None):
+ nom_obj = self.pool.get('barcode.nomenclature')
+ res = nom_obj.search(cr, uid, [], limit=1, context=context)
+ return res and res[0] or False
+
_defaults = {
'warehouse_id': _default_warehouse,
'active': True,
+ 'barcode_nomenclature_id': _get_default_nomenclature,
}
+class barcode_rule(models.Model):
+ _inherit = 'barcode.rule'
+
+ def _get_type_selection(self):
+ types = sets.Set(super(barcode_rule,self)._get_type_selection())
+ types.update([
+ ('weight','Weighted Product'),
+ ('location','Location'),
+ ('lot','Lot'),
+ ('package','Package')
+ ])
+ return list(types)
+
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: