from openerp.osv import fields, osv
from openerp.tools.translate import _
from openerp import tools
-from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
+from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT, DEFAULT_SERVER_DATE_FORMAT
from openerp import SUPERUSER_ID
import openerp.addons.decimal_precision as dp
import logging
'warehouse_selectable': fields.boolean('Applicable on Warehouse'),
'supplied_wh_id': fields.many2one('stock.warehouse', 'Supplied Warehouse'),
'supplier_wh_id': fields.many2one('stock.warehouse', 'Supplier Warehouse'),
+ 'company_id': fields.many2one('res.company', 'Company', select=1, help='Let this field empty if this route is shared between all companies'),
}
_defaults = {
'sequence': lambda self, cr, uid, ctx: 0,
'active': True,
'product_selectable': True,
+ 'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.location.route', context=c),
}
res[q.id] += ': ' + str(q.qty) + q.product_id.uom_id.name
return res
+ def _calc_inventory_value(self, cr, uid, ids, name, attr, context=None):
+ res = {}
+ uid_company_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id
+ for quant in self.browse(cr, uid, ids, context=context):
+ context.pop('force_company', None)
+ if quant.company_id.id != uid_company_id:
+ #if the company of the quant is different than the current user company, force the company in the context
+ #then re-do a browse to read the property fields for the good company.
+ context['force_company'] = quant.company_id.id
+ quant = self.browse(cr, uid, quant.id, context=context)
+ res[quant.id] = self._get_inventory_value(cr, uid, quant, context=context)
+ return res
+
+ def _get_inventory_value(self, cr, uid, quant, context=None):
+ return quant.product_id.standard_price * quant.qty
+
_columns = {
'name': fields.function(_get_quant_name, type='char', string='Identifier'),
'product_id': fields.many2one('product.product', 'Product', required=True),
# Used for negative quants to reconcile after compensated by a new positive one
'propagated_from_id': fields.many2one('stock.quant', 'Linked Quant', help='The negative quant this is coming from'),
'negative_dest_location_id': fields.many2one('stock.location', 'Destination Location', help='Technical field used to record the destination location of a move that created a negative quant'),
+ 'inventory_value': fields.function(_calc_inventory_value, string="Inventory Value", type='float', readonly=True),
}
_defaults = {
'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.quant', context=c),
}
+ def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
+ ''' Overwrite the read_group in order to sum the function field 'inventory_value' in group by'''
+ res = super(stock_quant, self).read_group(cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby)
+ if 'inventory_value' in fields:
+ for line in res:
+ if '__domain' in line:
+ lines = self.search(cr, uid, line['__domain'], context=context)
+ inv_value = 0.0
+ for line2 in self.browse(cr, uid, lines, context=context):
+ inv_value += line2.inventory_value
+ line['inventory_value'] = inv_value
+ return res
+
def action_view_quant_history(self, cr, uid, ids, context=None):
'''
This function returns an action that display the history of the quant, which
self.write(cr, SUPERUSER_ID, toreserve, {'reservation_id': move.id, 'link_move_operation_id': link and link.id or False}, context=context)
#check if move'state needs to be set as 'assigned'
move.refresh()
- if sum([q.qty for q in move.reserved_quant_ids]) == move.product_qty and move.state == 'confirmed':
+ if sum([q.qty for q in move.reserved_quant_ids]) == move.product_qty and move.state in ('confirmed', 'waiting'):
self.pool.get('stock.move').write(cr, uid, [move.id], {'state': 'assigned'}, context=context)
def quants_move(self, cr, uid, quants, move, lot_id=False, owner_id=False, src_package_id=False, dest_package_id=False, context=None):
'qty': qty,
'cost': price_unit,
'history_ids': [(4, move.id)],
- 'in_date': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
+ 'in_date': datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT),
'company_id': move.company_id.id,
'lot_id': lot_id,
'owner_id': owner_id,
def _price_update(self, cr, uid, ids, newprice, context=None):
self.write(cr, SUPERUSER_ID, ids, {'cost': newprice}, context=context)
- def write(self, cr, uid, ids, vals, context=None):
- #We want to trigger the move with nothing on reserved_quant_ids for the store of the remaining quantity
- if 'reservation_id' in vals:
- reservation_ids = self.browse(cr, uid, ids, context=context)
- moves_to_warn = set()
- for reser in reservation_ids:
- if reser.reservation_id:
- moves_to_warn.add(reser.reservation_id.id)
- self.pool.get('stock.move').write(cr, uid, list(moves_to_warn), {'reserved_quant_ids': []}, context=context)
- return super(stock_quant, self).write(cr, SUPERUSER_ID, ids, vals, context=context)
-
def quants_unreserve(self, cr, uid, move, context=None):
related_quants = [x.id for x in move.reserved_quant_ids]
return self.write(cr, SUPERUSER_ID, related_quants, {'reservation_id': False, 'link_move_operation_id': False}, context=context)
'''
domain += location and [('location_id', 'child_of', location.id)] or []
domain += [('product_id', '=', product.id)] + domain
+ #don't take into account location that are production, supplier or inventory
+ ignore_location_ids = self.pool.get('stock.location').search(cr, uid, [('usage', 'in', ('production', 'supplier', 'inventory'))], context=context)
+ domain.append(('location_id','not in',ignore_location_ids))
res = []
offset = 0
while quantity > 0:
raise osv.except_osv(_('Error'), _('You cannot move product %s to a location of type view %s.') % (record.product_id.name, record.location_id.name))
return True
- # FP Note: rehab this, with the auto creation algo
- # def _check_tracking(self, cr, uid, ids, context=None):
- # """ Checks if serial number is assigned to stock move or not.
- # @return: True or False
- # """
- # for move in self.browse(cr, uid, ids, context=context):
- # if not move.lot_id and \
- # (move.state == 'done' and \
- # ( \
- # (move.product_id.track_production and move.location_id.usage == 'production') or \
- # (move.product_id.track_production and move.location_dest_id.usage == 'production') or \
- # (move.product_id.track_incoming and move.location_id.usage == 'supplier') or \
- # (move.product_id.track_outgoing and move.location_dest_id.usage == 'customer') or \
- # (move.product_id.track_incoming and move.location_id.usage == 'inventory') \
- # )):
- # return False
- # return True
-
_constraints = [
(_check_location, 'You cannot move products to a location of the type view.', ['location_id'])
- #(_check_tracking, 'You must assign a serial number for this product.', ['prodlot_id']),
]
),
'priority': fields.selection([('0', 'Low'), ('1', 'Normal'), ('2', 'High')], string='Priority', required=True),
'min_date': fields.function(get_min_max_date, multi="min_max_date", fnct_inv=_set_min_date,
- store={'stock.move': (_get_pickings, ['state', 'date_expected'], 20)}, type='datetime', string='Scheduled Date', select=1, help="Scheduled time for the first part of the shipment to be processed"),
+ store={'stock.move': (_get_pickings, ['state', 'date_expected'], 20)}, type='datetime', string='Scheduled Date', select=1, help="Scheduled time for the first part of the shipment to be processed. Setting manually a value here would set it as expected date for all the stock moves.", track_visibility='onchange'),
'max_date': fields.function(get_min_max_date, multi="min_max_date",
store={'stock.move': (_get_pickings, ['state', 'date_expected'], 20)}, type='datetime', string='Max. Expected Date', select=2, help="Scheduled time for the last part of the shipment to be processed"),
- 'date': fields.datetime('Commitment Date', help="Date promised for the completion of the transfer order, usually set the time of the order and revised later on.", select=True, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}),
+ 'date': fields.datetime('Commitment Date', help="Date promised for the completion of the transfer order, usually set the time of the order and revised later on.", select=True, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, track_visibility='onchange'),
'date_done': fields.datetime('Date of Transfer', help="Date of Completion", states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}),
'move_lines': fields.one2many('stock.move', 'picking_id', 'Internal Moves', states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}),
'quant_reserved_exist': fields.function(_get_quant_reserved_exist, type='boolean', string='Quant already reserved ?', help='technical field used to know if there is already at least one quant reserved on moves of a given picking'),
'pack_operation_ids': fields.one2many('stock.pack.operation', 'picking_id', string='Related Packing Operations'),
'pack_operation_exist': fields.function(_get_pack_operation_exist, type='boolean', string='Pack Operation Exists?', help='technical field for attrs in view'),
'picking_type_id': fields.many2one('stock.picking.type', 'Picking Type', required=True),
+ 'picking_type_code': fields.related('picking_type_id', 'code', type='char', string='Picking Type Code', help="Technical field used to display the correct label on print button in the picking view"),
'owner_id': fields.many2one('res.partner', 'Owner', help="Default Owner"),
# Used to search on pickings
'state': 'draft',
'move_type': 'one',
'priority': '1', # normal
- 'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
+ 'date': fields.datetime.now,
'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.picking', context=c)
}
_sql_constraints = [
if not default.get('backorder_id'):
default['backorder_id'] = False
default['pack_operation_ids'] = []
+ default['date_done'] = False
return super(stock_picking, self).copy(cr, uid, id, default, context)
+ def do_print_delivery(self, cr, uid, ids, context=None):
+ '''This function prints the delivery order'''
+ assert len(ids) == 1, 'This option should only be used for a single id at a time'
+ datas = {
+ 'model': 'stock.picking',
+ 'ids': ids,
+ }
+ return {'type': 'ir.actions.report.xml', 'report_name': 'stock.picking.list', 'datas': datas, 'nodestroy': True}
+
+ def do_print_picking(self, cr, uid, ids, context=None):
+ '''This function prints the picking list'''
+ assert len(ids) == 1, 'This option should only be used for a single id at a time'
+ datas = {
+ 'model': 'stock.picking',
+ 'ids': ids,
+ }
+ return {'type': 'ir.actions.report.xml', 'report_name': 'stock.picking.list.internal', 'datas': datas, 'nodestroy': True}
+
def action_confirm(self, cr, uid, ids, context=None):
todo = []
todo_force_assign = []
return True
def cancel_assign(self, cr, uid, ids, context=None):
- """ Cancels picking and moves.
+ """ Cancels picking for the moves that are in the assigned state
@return: True
"""
for pick in self.browse(cr, uid, ids, context=context):
- move_ids = [x.id for x in pick.move_lines]
+ move_ids = [x.id for x in pick.move_lines if x not in ['done', 'cancel']]
self.pool.get('stock.move').cancel_assign(cr, uid, move_ids, context=context)
return True
move_obj = self.pool.get("stock.move")
move_obj.write(cr, uid, backorder_move_ids, {'picking_id': backorder_id}, context=context)
- self.pool.get("stock.picking").action_confirm(cr, uid, [picking.id], context=context)
+ self.write(cr, uid, [picking.id], {'date_done': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)}, context=context)
self.action_confirm(cr, uid, [backorder_id], context=context)
return backorder_id
return False
def _create_link_for_product(product_id, qty):
qty_to_assign = qty
for move in picking.move_lines:
- if move.product_id.id == product_id:
+ if move.product_id.id == product_id and move.state not in ['done', 'cancel']:
qty_on_link = min(move.remaining_qty, qty_to_assign)
link_obj.create(cr, uid, {'move_id': move.id, 'operation_id': op.id, 'qty': qty_on_link}, context=context)
qty_to_assign -= qty_on_link
+ move.refresh()
if qty_to_assign <= 0:
break
todo_move_ids = []
toassign_move_ids = []
for move in picking.move_lines:
- if move.state == 'draft':
+ if move.state in ('done', 'cancel'):
+ #ignore stock moves cancelled or already done
+ continue
+ elif move.state == 'draft':
toassign_move_ids.append(move.id)
if move.remaining_qty == 0:
if move.state in ('draft', 'assigned', 'confirmed'):
todo_move_ids.append(move.id)
#Assign move as it was assigned before
toassign_move_ids.append(new_move)
- else:
+ elif move.state:
#this should never happens
raise
self.rereserve_quants(cr, uid, picking, move_ids=todo_move_ids, context=context)
('name_ref_uniq', 'unique (name, ref)', 'The combination of Serial Number and internal reference must be unique !'),
]
+ def action_traceability(self, cr, uid, ids, context=None):
+ """ It traces the information of lots
+ @param self: The object pointer.
+ @param cr: A database cursor
+ @param uid: ID of the user currently logged in
+ @param ids: List of IDs selected
+ @param context: A standard dictionary
+ @return: A dictionary of values
+ """
+ quant_obj = self.pool.get("stock.quant")
+ move_obj = self.pool.get("stock.move")
+ quants = quant_obj.search(cr, uid, [('lot_id', 'in', ids)], context=context)
+ moves = set()
+ for quant in quant_obj.browse(cr, uid, quants, context=context):
+ moves |= {move.id for move in quant.history_ids}
+ if moves:
+ return {
+ 'domain': "[('id','in',["+','.join(map(str, list(moves)))+"])]",
+ 'name': _('Traceability'),
+ 'view_mode': 'tree,form',
+ 'view_type': 'form',
+ 'context': {'tree_view_ref': 'stock.view_move_tree'},
+ 'res_model': 'stock.move',
+ 'type': 'ir.actions.act_window',
+ }
+ return False
+
+
# ----------------------------------------------------
# Move
res.append((line.id, name))
return res
+ def create(self, cr, uid, vals, context=None):
+ if vals.get('product_id') and not vals.get('price_unit'):
+ prod_obj = self.pool.get('product.product')
+ vals['price_unit'] = prod_obj.browse(cr, uid, vals['product_id'], context=context).standard_price
+ return super(stock_move, self).create(cr, uid, vals, context=context)
+
def _quantity_normalize(self, cr, uid, ids, name, args, context=None):
uom_obj = self.pool.get('product.uom')
res = {}
res.add(quant.reservation_id.id)
return list(res)
+ def _get_move_ids(self, cr, uid, ids, context=None):
+ res = []
+ for picking in self.browse(cr, uid, ids, context=context):
+ res += [x.id for x in picking.move_lines]
+ return res
+
_columns = {
'name': fields.char('Description', required=True, select=True),
'priority': fields.selection([('0', 'Not urgent'), ('1', 'Urgent')], 'Priority'),
'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", states={'done': [('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"),
+ 'date_expected': fields.datetime('Expected 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)]}),
# TODO: improve store to add dependency on product UoM
'product_qty': fields.function(_quantity_normalize, type='float', store=True, string='Quantity',
'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_priority': fields.related('picking_id', 'priority', type='selection', selection=[('0', 'Low'), ('1', 'Normal'), ('2', 'High')], string='Picking Priority'),
+ 'picking_priority': fields.related('picking_id', 'priority', type='selection', selection=[('0', 'Low'), ('1', 'Normal'), ('2', 'High')], string='Picking Priority', store={'stock.picking': (_get_move_ids, ['priority'], 10)}),
'note': fields.text('Notes'),
'state': fields.selection([('draft', 'New'),
('cancel', 'Cancelled'),
"* Available: When products are reserved, it is set to \'Available\'.\n"\
"* Done: When the shipment is processed, the state is \'Done\'."),
- 'price_unit': fields.float('Unit Price', help="Technical field used to record the product cost set by the user during a picking confirmation (when average price costing method is used). Value given in company currency and in product uom."), # as it's a technical field, we intentionally don't provide the digits attribute
+ 'price_unit': fields.float('Unit Price', help="Technical field used to record the product cost set by the user during a picking confirmation (when costing method used is 'average price' or 'real'). Value given in company currency and in product uom."), # as it's a technical field, we intentionally don't provide the digits attribute
'company_id': fields.many2one('res.company', 'Company', required=True, select=True),
'backorder_id': fields.related('picking_id', 'backorder_id', type='many2one', relation="stock.picking", string="Back Order of", select=True),
'procurement_id': fields.many2one('procurement.order', 'Procurement'),
'group_id': fields.many2one('procurement.group', 'Procurement Group'),
'rule_id': fields.many2one('procurement.rule', 'Procurement Rule', help='The pull rule that created this stock move'),
+ 'push_rule_id': fields.many2one('stock.location.path', 'Push Rule', help='The push rule that created this stock move'),
'propagate': fields.boolean('Propagate cancel and split', help='If checked, when this move is cancelled, cancel the linked move too'),
'picking_type_id': fields.many2one('stock.picking.type', 'Picking Type'),
'inventory_id': fields.many2one('stock.inventory', 'Inventory'),
'product_qty': 1.0,
'product_uom_qty': 1.0,
'scrapped': False,
- 'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
+ 'date': fields.datetime.now,
'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.move', context=c),
- 'date_expected': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
+ 'date_expected': fields.datetime.now,
'procure_method': 'make_to_stock',
'propagate': True,
}
return super(stock_move, self).copy_data(cr, uid, id, default, context)
def do_unreserve(self, cr, uid, move_ids, context=None):
- ids_to_free = []
quant_obj = self.pool.get("stock.quant")
for move in self.browse(cr, uid, move_ids, context=context):
quant_obj.quants_unreserve(cr, uid, move, context=context)
+ putaway_values = []
+ for putaway_rec in move.putaway_ids:
+ putaway_values.append((2, putaway_rec.id))
+ self.write(cr, uid, [move.id], {'state': 'confirmed', 'putaway_ids': putaway_values}, context=context)
def _prepare_procurement_from_move(self, cr, uid, move, context=None):
origin = (move.group_id and (move.group_id.name + ":") or "") + (move.rule_id and move.rule_id.name or "/")
'warehouse_id': move.warehouse_id and move.warehouse_id.id or False,
}
- def _push_apply(self, cr, uid, moves, context):
+ def _push_apply(self, cr, uid, moves, context=None):
push_obj = self.pool.get("stock.location.path")
for move in moves:
if not move.move_dest_id:
- routes = [x.id for x in move.product_id.route_ids + move.product_id.categ_id.total_route_ids]
- routes = routes or [x.id for x in move.route_ids]
- if routes:
- domain = [('route_id', 'in', routes), ('location_from_id', '=', move.location_dest_id.id)]
- if move.warehouse_id:
- domain += [('warehouse_id', '=', move.warehouse_id.id)]
- rules = push_obj.search(cr, uid, domain, context=context)
- if rules:
- rule = push_obj.browse(cr, uid, rules[0], context=context)
- push_obj._apply(cr, uid, rule, move, context=context)
+ domain = [('location_from_id', '=', move.location_dest_id.id)]
+ if move.warehouse_id:
+ domain += ['|', ('warehouse_id', '=', move.warehouse_id.id), ('warehouse_id', '=', False)]
+ #priority goes to the route defined on the product and product category
+ route_ids = [x.id for x in move.product_id.route_ids + move.product_id.categ_id.total_route_ids]
+ rules = push_obj.search(cr, uid, domain + [('route_id', 'in', route_ids)], order='route_sequence, sequence', context=context)
+ if not rules:
+ #but if there's no rule matching, we try without filtering on routes
+ rules = push_obj.search(cr, uid, domain, order='route_sequence, sequence', context=context)
+ if rules:
+ rule = push_obj.browse(cr, uid, rules[0], context=context)
+ push_obj._apply(cr, uid, rule, move, context=context)
return True
# Create the stock.move.putaway records
- def _putaway_apply(self, cr, uid, ids, context=None):
+ def _putaway_apply(self, cr, uid, move, putaway, context=None):
+ # Should call different methods here in later versions
moveputaway_obj = self.pool.get('stock.move.putaway')
+ quant_obj = self.pool.get('stock.quant')
+ if putaway.method == 'fixed' and putaway.location_spec_id:
+ for row in quant_obj.read_group(cr, uid, [('reservation_id', '=', move.id)], ['qty', 'lot_id'], ['lot_id'], context=context):
+ vals = {
+ 'move_id': move.id,
+ 'location_id': putaway.location_spec_id.id,
+ 'quantity': row['qty'],
+ 'lot_id': row.get('lot_id') and row['lot_id'][0] or False,
+ }
+ moveputaway_obj.create(cr, SUPERUSER_ID, vals, context=context)
+
+ def _putaway_check(self, cr, uid, ids, context=None):
for move in self.browse(cr, uid, ids, context=context):
putaway = self.pool.get('stock.location').get_putaway_strategy(cr, uid, move.location_dest_id, move.product_id, context=context)
if putaway:
- # Should call different methods here in later versions
- # TODO: take care of lots
- if putaway.method == 'fixed' and putaway.location_spec_id:
- moveputaway_obj.create(cr, SUPERUSER_ID, {'move_id': move.id,
- 'location_id': putaway.location_spec_id.id,
- 'quantity': move.product_qty}, context=context)
- return True
+ self._putaway_apply(cr, uid, move, putaway, context=context)
def _create_procurement(self, cr, uid, move, context=None):
""" This will create a procurement order """
return self.pool.get("procurement.order").create(cr, uid, self._prepare_procurement_from_move(cr, uid, move, context=context))
- # Check that we do not modify a stock.move which is done
def write(self, cr, uid, ids, vals, context=None):
+ if context is None:
+ context = {}
if isinstance(ids, (int, long)):
ids = [ids]
+ # Check that we do not modify a stock.move which is done
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):
if move.state == 'done':
if frozen_fields.intersection(vals):
raise osv.except_osv(_('Operation Forbidden!'),
_('Quantities, Units of Measure, Products and Locations cannot be modified on stock moves that have already been processed (except by the Administrator).'))
+ propagated_changes_dict = {}
+ #propagation of quantity change
+ if vals.get('product_uom_qty'):
+ propagated_changes_dict['product_uom_qty'] = vals['product_uom_qty']
+ if vals.get('product_uom_id'):
+ propagated_changes_dict['product_uom_id'] = vals['product_uom_id']
+ #propagation of expected date:
+ propagated_date_field = False
+ if vals.get('date_expected'):
+ #propagate any manual change of the expected date
+ propagated_date_field = 'date_expected'
+ elif (vals.get('state', '') == 'done' and vals.get('date')):
+ #propagate also any delta observed when setting the move as done
+ propagated_date_field = 'date'
+
+ if not context.get('do_not_propagate', False) and (propagated_date_field or propagated_changes_dict):
+ #any propagation is (maybe) needed
+ for move in self.browse(cr, uid, ids, context=context):
+ if move.move_dest_id and move.propagate:
+ if 'date_expected' in propagated_changes_dict:
+ propagated_changes_dict.pop('date_expected')
+ if propagated_date_field:
+ current_date = datetime.strptime(move.date_expected, DEFAULT_SERVER_DATETIME_FORMAT)
+ new_date = datetime.strptime(vals.get(propagated_date_field), DEFAULT_SERVER_DATETIME_FORMAT)
+ delta = new_date - current_date
+ if abs(delta.days) >= move.company_id.propagation_minimum_delta:
+ old_move_date = datetime.strptime(move.move_dest_id.date_expected, DEFAULT_SERVER_DATETIME_FORMAT)
+ new_move_date = (old_move_date + relativedelta.relativedelta(days=delta.days or 0)).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
+ propagated_changes_dict['date_expected'] = new_move_date
+ #For pushed moves as well as for pulled moves, propagate by recursive call of write().
+ #Note that, for pulled moves we intentionally don't propagate on the procurement.
+ if propagated_changes_dict:
+ self.write(cr, uid, [move.move_dest_id.id], propagated_changes_dict, context=context)
return super(stock_move, self).write(cr, uid, ids, vals, context=context)
def onchange_quantity(self, cr, uid, ids, product_id, product_qty, product_uom, product_uos):
'company_id': move.company_id and move.company_id.id or False,
'move_type': move.group_id and move.group_id.move_type or 'one',
'partner_id': move.group_id and move.group_id.partner_id and move.group_id.partner_id.id or False,
- 'date_done': move.date_expected,
'picking_type_id': move.picking_type_id and move.picking_type_id.id or False,
}
pick = pick_obj.create(cr, uid, values, context=context)
@return: Move Date
"""
if not date_expected:
- date_expected = time.strftime('%Y-%m-%d %H:%M:%S')
+ date_expected = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
return {'value': {'date': date_expected}}
def action_confirm(self, cr, uid, ids, context=None):
""" Changes the state to assigned.
@return: True
"""
- self.action_assign(cr, uid, ids, context=context)
- self.write(cr, uid, ids, {'state': 'assigned'})
- return True
+ return self.write(cr, uid, ids, {'state': 'assigned'})
def cancel_assign(self, cr, uid, ids, context=None):
""" Changes the state to confirmed.
"""
return self.write(cr, uid, ids, {'state': 'confirmed'})
+ def check_tracking(self, cr, uid, move, lot_id, context=None):
+ """ Checks if serial number is assigned to stock move or not and raise an error if it had to.
+ """
+ check = False
+ if move.product_id.track_all and not move.location_dest_id.usage == 'inventory':
+ check = True
+ elif move.product_id.track_incoming and move.location_id.usage in ('supplier', 'transit', 'inventory') and move.location_dest_id.usage == 'internal':
+ check = True
+ elif move.product_id.track_outgoing and move.location_dest_id.usage in ('customer', 'transit') and move.location_id.usage == 'internal':
+ check = True
+ if check and not lot_id:
+ raise osv.except_osv(_('Warning!'), _('You must assign a serial number for the product %s') % (move.product_id.name))
+
def action_assign(self, cr, uid, ids, context=None):
""" Checks the product type and accordingly writes the state.
- @return: No. of moves done
"""
context = context or {}
quant_obj = self.pool.get("stock.quant")
- done = []
+ to_assign_moves = []
for move in self.browse(cr, uid, ids, context=context):
if move.state not in ('confirmed', 'waiting', 'assigned'):
continue
if move.product_id.type == 'consu':
- done.append(move.id)
+ to_assign_moves.append(move.id)
continue
else:
#build the prefered domain based on quants that moved in previous linked done move
quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, move.product_id, qty, domain=main_domain, prefered_domain=prefered_domain, fallback_domain=fallback_domain, restrict_lot_id=move.restrict_lot_id.id, restrict_partner_id=move.restrict_partner_id.id, context=context)
quant_obj.quants_reserve(cr, uid, quants, move, context=context)
- self._putaway_apply(cr, uid, ids, context=context)
+ #force assignation of consumable products
+ if to_assign_moves:
+ self.force_assign(cr, uid, to_assign_moves, context=context)
+ #check if a putaway rule is likely to be processed and store result on the move
+ self._putaway_check(cr, uid, ids, context=context)
def action_cancel(self, cr, uid, ids, context=None):
""" Cancels the moves and if all moves are cancelled it cancels the picking.
@return:
"""
context = context or {}
+ picking_obj = self.pool.get("stock.picking")
quant_obj = self.pool.get("stock.quant")
pack_op_obj = self.pool.get("stock.pack.operation")
todo = [move.id for move in self.browse(cr, uid, ids, context=context) if move.state == "draft"]
fallback_domain = [('reservation_id', '=', False)]
#first, process the move per linked operation first because it may imply some specific domains to consider
for record in move.linked_move_operation_ids:
+ self.check_tracking(cr, uid, move, record.operation_id.lot_id.id, context=context)
dom = main_domain + self.pool.get('stock.move.operation.link').get_specific_domain(cr, uid, record, context=context)
- quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, move.product_id, record.qty, domain=dom, prefered_domain=prefered_domain, fallback_domain=fallback_domain, context=context)
+ quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, move.product_id, record.qty, domain=dom, prefered_domain=prefered_domain, fallback_domain=fallback_domain, restrict_lot_id=move.restrict_lot_id.id, restrict_partner_id=move.restrict_partner_id.id, context=context)
package_id = False
if not record.operation_id.package_id:
#if a package and a result_package is given, we don't enter here because it will be processed by process_packaging() later
qty -= record.qty
#then if the total quantity processed this way isn't enough, process the remaining quantity without any specific domain
if qty > 0:
- quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, move.product_id, qty, domain=main_domain, prefered_domain=prefered_domain, fallback_domain=fallback_domain, context=context)
- quant_obj.quants_move(cr, uid, quants, move, context=context)
+ self.check_tracking(cr, uid, move, move.restrict_lot_id.id, context=context)
+ quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, move.product_id, qty, domain=main_domain, prefered_domain=prefered_domain, fallback_domain=fallback_domain, restrict_lot_id=move.restrict_lot_id.id, restrict_partner_id=move.restrict_partner_id.id, context=context)
+ quant_obj.quants_move(cr, uid, quants, move, lot_id=move.restrict_lot_id.id, owner_id=move.restrict_partner_id.id, context=context)
#unreserve the quants and make them available for other operations/moves
quant_obj.quants_unreserve(cr, uid, move, context=context)
procurement_ids.append(move.procurement_id.id)
self.write(cr, uid, ids, {'state': 'done', 'date': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)}, context=context)
self.pool.get('procurement.order').check(cr, uid, procurement_ids, context=context)
+ #check picking state to set the date_done is needed
+ done_picking = []
+ for picking in picking_obj.browse(cr, uid, list(pickings), context=context):
+ if picking.state == 'done' and not picking.date_done:
+ done_picking.append(picking.id)
+ if done_picking:
+ picking_obj.write(cr, uid, done_picking, {'date_done': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)}, context=context)
return True
def unlink(self, cr, uid, ids, context=None):
raise osv.except_osv(_('User Error!'), _('You can only delete draft moves.'))
return super(stock_move, self).unlink(cr, uid, ids, context=context)
- def action_scrap(self, cr, uid, ids, quantity, location_id, context=None):
+ def action_scrap(self, cr, uid, ids, quantity, location_id, restrict_lot_id=False, restrict_partner_id=False, context=None):
""" Move the scrap/damaged product into scrap location
@param cr: the database cursor
@param uid: the user id
'state': move.state,
'scrapped': True,
'location_dest_id': location_id,
- #TODO lot_id is now on quant and not on move, need to do something for this
- #'lot_id': move.lot_id.id,
+ 'restrict_lot_id': restrict_lot_id,
+ 'restrict_partner_id': restrict_partner_id,
}
new_move = self.copy(cr, uid, move.id, default_val)
self.action_done(cr, uid, res, context=context)
return res
- def action_consume(self, cr, uid, ids, quantity, location_id=False, context=None):
- """ Consumed product with specific quatity from specific source location
- @param cr: the database cursor
- @param uid: the user id
+ def action_consume(self, cr, uid, ids, quantity, location_id=False, restrict_lot_id=False, restrict_partner_id=False, context=None):
+ """ Consumed product with specific quantity from specific source location. This correspond to a split of the move (or write if the quantity to consume is >= than the quantity of the move) followed by an action_done
@param ids: ids of stock move object to be consumed
- @param quantity : specify consume quantity
+ @param quantity : specify consume quantity (given in move UoM)
@param location_id : specify source location
- @param context: context arguments
@return: Consumed lines
"""
- #quantity should be given in MOVE UOM
+ uom_obj = self.pool.get('product.uom')
if context is None:
context = {}
if quantity <= 0:
res = []
for move in self.browse(cr, uid, ids, context=context):
move_qty = move.product_qty
+ uom_qty = uom_obj._compute_qty(cr, uid, move.product_id.uom_id.id, quantity, move.product_uom.id)
if move_qty <= 0:
raise osv.except_osv(_('Error!'), _('Cannot 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
- if quantity_rest <= 0:
- quantity_rest = 0
- uos_qty_rest = 0
- quantity = move.product_qty
-
- uos_qty = quantity / move_qty * move.product_uos_qty
+ quantity_rest = move.product_qty - uom_qty
if quantity_rest > 0:
- default_val = {
- 'product_uom_qty': quantity,
- 'product_uos_qty': uos_qty,
- 'state': move.state,
- 'location_id': location_id or move.location_id.id,
- }
- current_move = self.copy(cr, uid, move.id, default_val)
- res += [current_move]
- update_val = {}
- update_val['product_uom_qty'] = quantity_rest
- update_val['product_uos_qty'] = uos_qty_rest
- self.write(cr, uid, [move.id], update_val)
-
+ ctx = context.copy()
+ if location_id:
+ ctx['source_location_id'] = location_id
+ res.append(self.split(cr, uid, move, move_qty - quantity_rest, restrict_lot_id=restrict_lot_id,
+ restrict_partner_id=restrict_partner_id, context=ctx))
else:
- quantity_rest = quantity
- uos_qty_rest = uos_qty
- res += [move.id]
- update_val = {
- 'product_uom_qty' : quantity_rest,
- 'product_uos_qty' : uos_qty_rest,
- 'location_id': location_id or move.location_id.id,
- }
- self.write(cr, uid, [move.id], update_val)
+ res.append(move.id)
+ if location_id:
+ self.write(cr, uid, [move.id], {'location_id': location_id, 'restrict_lot_id': restrict_lot_id,
+ 'restrict_partner_id': restrict_partner_id}, context=context)
self.action_done(cr, uid, res, context=context)
return res
- def split(self, cr, uid, move, qty, context=None):
- """ Splits qty from move move into a new move """
- if move.product_qty == qty:
+ def split(self, cr, uid, move, qty, restrict_lot_id=False, restrict_partner_id=False, context=None):
+ """ Splits qty from move move into a new move
+ :param move: browse record
+ :param qty: float. quantity to split (given in product UoM)
+ :param context: dictionay. can contains the special key 'source_location_id' in order to force the source location when copying the move
+ """
+ if move.product_qty <= qty or qty == 0:
return move.id
- if (move.product_qty < qty) or (qty == 0):
- return False
uom_obj = self.pool.get('product.uom')
context = context or {}
'product_uom_qty': uom_qty,
'product_uos_qty': uos_qty,
'state': move.state,
+ 'procure_method': 'make_to_stock',
'move_dest_id': False,
- 'reserved_quant_ids': []
+ 'reserved_quant_ids': [],
+ 'restrict_lot_id': restrict_lot_id,
+ 'restrict_partner_id': restrict_partner_id
}
+ if context.get('source_location_id'):
+ defaults['location_id'] = context['source_location_id']
new_move = self.copy(cr, uid, move.id, defaults)
+ ctx = context.copy()
+ ctx['do_not_propagate'] = True
self.write(cr, uid, [move.id], {
'product_uom_qty': move.product_uom_qty - uom_qty,
'product_uos_qty': move.product_uos_qty - uos_qty,
#'reserved_quant_ids': [(6,0,[])] SHOULD NOT CHANGE as it has been reserved already
- }, context=context)
+ }, context=ctx)
if move.move_dest_id and move.propagate:
new_move_prop = self.split(cr, uid, move.move_dest_id, qty, context=context)
:rtype: list of tuple
"""
#default available choices
- res_filter = [('none', ' All products of a whole location'), ('product', 'One product only')]
+ res_filter = [('none', _('All products of a whole location')), ('product', _('One product only'))]
settings_obj = self.pool.get('stock.config.settings')
config_ids = settings_obj.search(cr, uid, [], limit=1, order='id DESC', context=context)
#If we don't have updated config until now, all fields are by default false and so should be not dipslayed
if stock_settings.group_stock_tracking_owner:
res_filter.append(('owner', _('One owner only')))
res_filter.append(('product_owner', _('One product for a specific owner')))
- if stock_settings.group_stock_production_lot:
- res_filter.append(('lot', _('One Lot/Serial Number')))
if stock_settings.group_stock_tracking_lot:
+ res_filter.append(('lot', _('One Lot/Serial Number')))
+ if stock_settings.group_stock_packaging:
res_filter.append(('pack', _('A Pack')))
return res_filter
'move_ids': fields.one2many('stock.move', 'inventory_id', 'Created Moves', help="Inventory Moves."),
'state': fields.selection([('draft', 'Draft'), ('cancel', 'Cancelled'), ('confirm', 'In Progress'), ('done', 'Validated')], 'Status', readonly=True, select=True),
'company_id': fields.many2one('res.company', 'Company', required=True, select=True, readonly=True, states={'draft': [('readonly', False)]}),
- 'location_id': fields.many2one('stock.location', 'Location', required=True),
+ 'location_id': fields.many2one('stock.location', 'Location', required=True, readonly=True, states={'draft': [('readonly', False)]}),
'product_id': fields.many2one('product.product', 'Product', readonly=True, states={'draft': [('readonly', False)]}, help="Specify Product to focus your inventory on a particular Product."),
'package_id': fields.many2one('stock.quant.package', 'Pack', readonly=True, states={'draft': [('readonly', False)]}, help="Specify Pack to focus your inventory on a particular Pack."),
'partner_id': fields.many2one('res.partner', 'Owner', readonly=True, states={'draft': [('readonly', False)]}, help="Specify Owner to focus your inventory on a particular Owner."),
return False
_defaults = {
- 'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
+ 'date': fields.datetime.now,
'state': 'draft',
'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.inventory', context=c),
'location_id': _default_stock_location,
move_obj.action_done(cr, uid, [x.id for x in inv.move_ids if x.location_id.usage == 'internal'], context=context)
#then, we move from inventory loss. This 2 steps process is needed because some moved quant may need to be put again in stock
move_obj.action_done(cr, uid, [x.id for x in inv.move_ids if x.location_id.usage != 'internal'], context=context)
- self.write(cr, uid, [inv.id], {'state': 'done', 'date_done': time.strftime('%Y-%m-%d %H:%M:%S')}, context=context)
+ self.write(cr, uid, [inv.id], {'state': 'done', 'date_done': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)}, context=context)
return True
def _create_stock_move(self, cr, uid, inventory, todo_line, context=None):
return True
def action_cancel_inventory(self, cr, uid, ids, context=None):
- #TODO test
self.action_cancel_draft(cr, uid, ids, context=context)
def prepare_inventory(self, cr, uid, ids, context=None):
_name = "stock.inventory.line"
_description = "Inventory Line"
_rec_name = "inventory_id"
+ _order = "inventory_id, location_name, product_code, product_name, prod_lot_id"
+
+ def _get_product_name_change(self, cr, uid, ids, context=None):
+ return self.pool.get('stock.inventory.line').search(cr, uid, [('product_id', 'in', ids)], context=context)
+
+ def _get_location_change(self, cr, uid, ids, context=None):
+ return self.pool.get('stock.inventory.line').search(cr, uid, [('location_id', 'in', ids)], context=context)
+
_columns = {
'inventory_id': fields.many2one('stock.inventory', 'Inventory', ondelete='cascade', select=True),
'location_id': fields.many2one('stock.location', 'Location', required=True, select=True),
'state': fields.related('inventory_id', 'state', type='char', string='Status', readonly=True),
'th_qty': fields.float('Theoretical Quantity', readonly=True),
'partner_id': fields.many2one('res.partner', 'Owner'),
+ 'product_name': fields.related('product_id', 'name', type='char', string='Product name', store={
+ 'product.product': (_get_product_name_change, ['name', 'default_code'], 20),
+ 'stock.inventory.line': (lambda self, cr, uid, ids, c={}: ids, ['product_id'], 20),}),
+ 'product_code': fields.related('product_id', 'default_code', type='char', string='Product code', store={
+ 'product.product': (_get_product_name_change, ['name', 'default_code'], 20),
+ 'stock.inventory.line': (lambda self, cr, uid, ids, c={}: ids, ['product_id'], 20),}),
+ 'location_name': fields.related('location_id', 'complete_name', type='char', string='Location name', store={
+ 'stock.location': (_get_location_change, ['name', 'location_id', 'active'], 20),
+ 'stock.inventory.line': (lambda self, cr, uid, ids, c={}: ids, ['location_id'], 20),}),
}
_defaults = {
try:
mto_route_id = data_obj.get_object_reference(cr, uid, 'stock', 'route_warehouse0_mto')[1]
except:
- mto_route_id = route_obj.search(cr, uid, [('name', 'like', _('MTO'))], context=context)
+ mto_route_id = route_obj.search(cr, uid, [('name', 'like', _('Make To Order'))], context=context)
mto_route_id = mto_route_id and mto_route_id[0] or False
if not mto_route_id:
- raise osv.except_osv(_('Error!'), _('Can\'t find any generic MTO route.'))
+ raise osv.except_osv(_('Error!'), _('Can\'t find any generic Make To Order route.'))
from_loc, dest_loc, pick_type_id = values[0]
return {
pull_rule['procure_method'] = 'make_to_order'
pull_obj.create(cr, uid, vals=pull_rule, context=context)
- #create MTS route and pull rules for delivery a specific route MTO to be set on the product
+ #create MTS route and pull rules for delivery and a specific route MTO to be set on the product
route_name, values = routes_dict[warehouse.delivery_steps]
route_vals = self._get_reception_delivery_route(cr, uid, warehouse, route_name, context=context)
#create the route and its pull rules
output_loc = warehouse.lot_stock_id
picking_type_obj.write(cr, uid, warehouse.in_type_id.id, {'default_location_dest_id': input_loc.id}, context=context)
picking_type_obj.write(cr, uid, warehouse.out_type_id.id, {'default_location_src_id': output_loc.id}, context=context)
- picking_type_obj.write(cr, uid, warehouse.int_type_id.id, {'active': new_reception_step != 'one_step'}, context=context)
picking_type_obj.write(cr, uid, warehouse.pick_type_id.id, {'active': new_delivery_step != 'ship_only'}, context=context)
picking_type_obj.write(cr, uid, warehouse.pack_type_id.id, {'active': new_delivery_step == 'pick_pack_ship'}, context=context)
vals[values['field']] = location_id
#create new sequences
- in_seq_id = seq_obj.create(cr, SUPERUSER_ID, values={'name': vals.get('name', '') + _(' Sequence in'), 'prefix': vals.get('code', '') + '\IN\\', 'padding': 5}, context=context)
- out_seq_id = seq_obj.create(cr, SUPERUSER_ID, values={'name': vals.get('name', '') + _(' Sequence out'), 'prefix': vals.get('code', '') + '\OUT\\', 'padding': 5}, context=context)
- pack_seq_id = seq_obj.create(cr, SUPERUSER_ID, values={'name': vals.get('name', '') + _(' Sequence packing'), 'prefix': vals.get('code', '') + '\PACK\\', 'padding': 5}, context=context)
- pick_seq_id = seq_obj.create(cr, SUPERUSER_ID, values={'name': vals.get('name', '') + _(' Sequence picking'), 'prefix': vals.get('code', '') + '\PICK\\', 'padding': 5}, context=context)
- int_seq_id = seq_obj.create(cr, SUPERUSER_ID, values={'name': vals.get('name', '') + _(' Sequence internal'), 'prefix': vals.get('code', '') + '\INT\\', 'padding': 5}, context=context)
+ in_seq_id = seq_obj.create(cr, SUPERUSER_ID, values={'name': vals.get('name', '') + _(' Sequence in'), 'prefix': vals.get('code', '') + '/IN/', 'padding': 5}, context=context)
+ out_seq_id = seq_obj.create(cr, SUPERUSER_ID, values={'name': vals.get('name', '') + _(' Sequence out'), 'prefix': vals.get('code', '') + '/OUT/', 'padding': 5}, context=context)
+ pack_seq_id = seq_obj.create(cr, SUPERUSER_ID, values={'name': vals.get('name', '') + _(' Sequence packing'), 'prefix': vals.get('code', '') + '/PACK/', 'padding': 5}, context=context)
+ pick_seq_id = seq_obj.create(cr, SUPERUSER_ID, values={'name': vals.get('name', '') + _(' Sequence picking'), 'prefix': vals.get('code', '') + '/PICK/', 'padding': 5}, context=context)
+ int_seq_id = seq_obj.create(cr, SUPERUSER_ID, values={'name': vals.get('name', '') + _(' Sequence internal'), 'prefix': vals.get('code', '') + '/INT/', 'padding': 5}, context=context)
#create WH
new_id = super(stock_warehouse, self).create(cr, uid, vals=vals, context=context)
output_loc = wh_stock_loc
#choose the next available color for the picking types of this warehouse
- all_used_colors = self.pool.get('stock.picking.type').search_read(cr, uid, [('warehouse_id', '!=', False), ('color', '!=', False)], ['color'], order='color')
- not_used_colors = list(set(range(0, 9)) - set([x['color'] for x in all_used_colors]))
color = 0
- if not_used_colors:
- color = not_used_colors[0]
+ available_colors = [c%9 for c in range(3, 12)] # put flashy colors first
+ all_used_colors = self.pool.get('stock.picking.type').search_read(cr, uid, [('warehouse_id', '!=', False), ('color', '!=', False)], ['color'], order='color')
+ #don't use sets to preserve the list order
+ for x in all_used_colors:
+ if x['color'] in available_colors:
+ available_colors.remove(x['color'])
+ if available_colors:
+ color = available_colors[0]
+
+ #order the picking types with a sequence allowing to have the following suit for each warehouse: reception, internal, pick, pack, ship.
+ max_sequence = self.pool.get('stock.picking.type').search_read(cr, uid, [], ['sequence'], order='sequence desc')
+ max_sequence = max_sequence and max_sequence[0]['sequence'] or 0
in_type_id = picking_type_obj.create(cr, uid, vals={
'name': _('Receptions'),
'sequence_id': in_seq_id,
'default_location_src_id': supplier_loc.id,
'default_location_dest_id': input_loc.id,
+ 'sequence': max_sequence + 1,
'color': color}, context=context)
out_type_id = picking_type_obj.create(cr, uid, vals={
'name': _('Delivery Orders'),
'warehouse_id': new_id,
'code': 'outgoing',
'sequence_id': out_seq_id,
- 'delivery': True,
+ 'return_picking_type_id': in_type_id,
'default_location_src_id': output_loc.id,
'default_location_dest_id': customer_loc.id,
+ 'sequence': max_sequence + 4,
'color': color}, context=context)
+ picking_type_obj.write(cr, uid, [in_type_id], {'return_picking_type_id': out_type_id}, context=context)
int_type_id = picking_type_obj.create(cr, uid, vals={
'name': _('Internal Transfers'),
'warehouse_id': new_id,
'sequence_id': int_seq_id,
'default_location_src_id': wh_stock_loc.id,
'default_location_dest_id': wh_stock_loc.id,
- 'active': reception_steps != 'one_step',
- 'pack': False,
+ 'active': True,
+ 'sequence': max_sequence + 2,
'color': color}, context=context)
pack_type_id = picking_type_obj.create(cr, uid, vals={
'name': _('Pack'),
'default_location_src_id': wh_pack_stock_loc.id,
'default_location_dest_id': output_loc.id,
'active': delivery_steps == 'pick_pack_ship',
- 'pack': True,
+ 'sequence': max_sequence + 3,
'color': color}, context=context)
pick_type_id = picking_type_obj.create(cr, uid, vals={
'name': _('Pick'),
'default_location_src_id': wh_stock_loc.id,
'default_location_dest_id': wh_pack_stock_loc.id,
'active': delivery_steps != 'ship_only',
- 'pack': False,
+ 'sequence': max_sequence + 2,
'color': color}, context=context)
#write picking types on WH
'crossdock': (_('Cross-Dock'), [(warehouse.wh_input_stock_loc_id, warehouse.wh_output_stock_loc_id, warehouse.int_type_id.id), (warehouse.wh_output_stock_loc_id, customer_loc, warehouse.out_type_id.id)]),
'ship_only': (_('Ship Only'), [(warehouse.lot_stock_id, customer_loc, warehouse.out_type_id.id)]),
'pick_ship': (_('Pick + Ship'), [(warehouse.lot_stock_id, warehouse.wh_output_stock_loc_id, warehouse.pick_type_id.id), (warehouse.wh_output_stock_loc_id, customer_loc, warehouse.out_type_id.id)]),
- 'pick_pack_ship': (_('Pick + Pack + Ship'), [(warehouse.lot_stock_id, warehouse.wh_pack_stock_loc_id, warehouse.int_type_id.id), (warehouse.wh_pack_stock_loc_id, warehouse.wh_output_stock_loc_id, warehouse.pack_type_id.id), (warehouse.wh_output_stock_loc_id, customer_loc, warehouse.out_type_id.id)]),
+ 'pick_pack_ship': (_('Pick + Pack + Ship'), [(warehouse.lot_stock_id, warehouse.wh_pack_stock_loc_id, warehouse.pick_type_id.id), (warehouse.wh_pack_stock_loc_id, warehouse.wh_output_stock_loc_id, warehouse.pack_type_id.id), (warehouse.wh_output_stock_loc_id, customer_loc, warehouse.out_type_id.id)]),
}
def _handle_renaming(self, cr, uid, warehouse, name, context=None):
ids = [ids]
seq_obj = self.pool.get('ir.sequence')
route_obj = self.pool.get('stock.location.route')
- warehouse_obj = self.pool.get('stock.warehouse')
context_with_inactive = context.copy()
context_with_inactive['active_test'] = False
old_ids = set([wh.id for wh in warehouse.resupply_wh_ids])
to_add_wh_ids = new_ids - old_ids
if to_add_wh_ids:
- supplier_warehouses = warehouse_obj.browse(cr, uid, list(to_add_wh_ids), context=context)
+ supplier_warehouses = self.browse(cr, uid, list(to_add_wh_ids), context=context)
self._create_resupply_routes(cr, uid, warehouse, supplier_warehouses, warehouse.default_resupply_wh_id, context=context)
to_remove_wh_ids = old_ids - new_ids
if to_remove_wh_ids:
result[push_rule.id] = True
return result.keys()
+ def _get_rules(self, cr, uid, ids, context=None):
+ res = []
+ for route in self.browse(cr, uid, ids, context=context):
+ res += [x.id for x in route.push_ids]
+ return res
+
_columns = {
'name': fields.char('Operation Name', size=64, required=True),
'company_id': fields.many2one('res.company', 'Company'),
'stock.location.path': (lambda self, cr, uid, ids, c={}: ids, ['route_id'], 20),},
help="If the active field is set to False, it will allow you to hide the rule without removing it." ),
'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse'),
+ 'route_sequence': fields.related('route_id', 'sequence', string='Route Sequence',
+ store={
+ 'stock.location.route': (_get_rules, ['sequence'], 10),
+ 'stock.location.path': (lambda self, cr, uid, ids, c={}: ids, ['route_id'], 10),
+ }),
+ 'sequence': fields.integer('Sequence'),
}
_defaults = {
'auto': 'auto',
}
def _apply(self, cr, uid, rule, move, context=None):
move_obj = self.pool.get('stock.move')
- newdate = (datetime.strptime(move.date, '%Y-%m-%d %H:%M:%S') + relativedelta.relativedelta(days=rule.delay or 0)).strftime('%Y-%m-%d')
- if rule.auto=='transparent':
+ newdate = (datetime.strptime(move.date_expected, DEFAULT_SERVER_DATETIME_FORMAT) + relativedelta.relativedelta(days=rule.delay or 0)).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
+ if rule.auto == 'transparent':
+ old_dest_location = move.location_dest_id.id
move_obj.write(cr, uid, [move.id], {
'date': newdate,
+ 'date_expected': newdate,
'location_dest_id': rule.location_dest_id.id
})
- if rule.location_dest_id.id<>move.location_dest_id.id:
- move_obj._push_apply(self, cr, uid, move.id, context)
+ 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
+ move_obj._push_apply(cr, uid, [move], context=context)
return move.id
else:
move_id = move_obj.copy(cr, uid, move.id, {
'location_id': move.location_dest_id.id,
'location_dest_id': rule.location_dest_id.id,
- 'date': datetime.now().strftime('%Y-%m-%d'),
+ 'date': newdate,
'company_id': rule.company_id and rule.company_id.id or False,
'date_expected': newdate,
'picking_id': False,
'picking_type_id': rule.picking_type_id and rule.picking_type_id.id or False,
- 'rule_id': rule.id,
- 'propagate': rule.propagate,
+ 'propagate': rule.propagate,
+ 'push_rule_id': rule.id,
'warehouse_id': rule.warehouse_id and rule.warehouse_id.id or False,
})
move_obj.write(cr, uid, [move.id], {
# -------------------------
from openerp.report import report_sxw
-report_sxw.report_sxw('report.stock.quant.package.barcode', 'stock.quant.package', 'addons/stock/report/picking_barcode.rml')
+report_sxw.report_sxw('report.stock.quant.package.barcode', 'stock.quant.package', 'addons/stock/report/package_barcode.rml')
class stock_package(osv.osv):
"""
if quant:
if operation.product_id:
#if a product + a package information is given, we consider that we took a part of an existing package (unpacking)
- quant_obj.write(cr, uid, quant.id, {'package_id': operation.result_package_id.id}, context=context)
+ quant_obj.write(cr, SUPERUSER_ID, quant.id, {'package_id': operation.result_package_id.id}, context=context)
elif operation.package_id and operation.result_package_id:
#move the whole pack into the final package if any
pack_obj.write(cr, uid, [operation.package_id.id], {'parent_id': operation.result_package_id.id}, context=context)
def get_draft_procurements(self, cr, uid, ids, context=None):
if context is None:
context = {}
- result = {}
if not isinstance(ids, list):
ids = [ids]
procurement_obj = self.pool.get('procurement.order')
class stock_picking_type(osv.osv):
_name = "stock.picking.type"
_description = "The picking type determines the picking view"
+ _order = 'sequence'
def __get_bar_values(self, cr, uid, obj, domain, read_fields, value_field, groupby_field, context=None):
""" Generic method to generate data for bar chart values using SparklineBarWidget.
"""
month_begin = date.today().replace(day=1)
section_result = [{
- 'value': 0,
- 'tooltip': (month_begin + relativedelta.relativedelta(months=-i)).strftime('%B'),
- } for i in range(10, -1, -1)]
+ 'value': 0,
+ 'tooltip': (month_begin + relativedelta.relativedelta(months=i)).strftime('%B'),
+ } for i in range(-2, 2, 1)]
group_obj = obj.read_group(cr, uid, domain, read_fields, groupby_field, context=context)
for group in group_obj:
- group_begin_date = datetime.strptime(group['__domain'][0][2], tools.DEFAULT_SERVER_DATE_FORMAT)
+ group_begin_date = datetime.strptime(group['__domain'][0][2], DEFAULT_SERVER_DATE_FORMAT)
month_delta = relativedelta.relativedelta(month_begin, group_begin_date)
- section_result[10 - (month_delta.months + 1)] = {'value': group.get(value_field, 0), 'tooltip': group_begin_date.strftime('%B')}
+ section_result[-month_delta.months + 2] = {'value': group.get(value_field, 0), 'tooltip': group_begin_date.strftime('%B')}
+ inner_groupby = (group.get('__context', {})).get('group_by',[])
+ if inner_groupby:
+ groupby_picking = obj.read_group(cr, uid, group.get('__domain'), read_fields, inner_groupby, context=context)
+ for groupby in groupby_picking:
+ section_result[-month_delta.months + 2]['value'] = groupby.get(value_field, 0)
return section_result
- def _get_picking_data(self, cr, uid, ids, field_name, arg, context=None):
+ def _get_tristate_values(self, cr, uid, ids, field_name, arg, context=None):
+ picking_obj = self.pool.get('stock.picking')
+ res = dict.fromkeys(ids, [])
+ for picking_type_id in ids:
+ #get last 10 pickings of this type
+ picking_ids = picking_obj.search(cr, uid, [('picking_type_id', '=', picking_type_id), ('state', '=', 'done')], order='date_done desc', limit=10, context=context)
+ tristates = []
+ for picking in picking_obj.browse(cr, uid, picking_ids, context=context):
+ if picking.date_done > picking.date:
+ tristates.insert(0, {'tooltip': picking.name + _(': Late'), 'value': -1})
+ elif picking.backorder_id:
+ tristates.insert(0, {'tooltip': picking.name + _(': Backorder exists'), 'value': 0})
+ else:
+ tristates.insert(0, {'tooltip': picking.name + _(': OK'), 'value': 1})
+ res[picking_type_id] = tristates
+ return res
+
+
+
+ def _get_monthly_pickings(self, cr, uid, ids, field_name, arg, context=None):
obj = self.pool.get('stock.picking')
res = dict.fromkeys(ids, False)
month_begin = date.today().replace(day=1)
- groupby_begin = (month_begin + relativedelta.relativedelta(months=-4)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
- groupby_end = (month_begin + relativedelta.relativedelta(months=3)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
+ groupby_begin = (month_begin + relativedelta.relativedelta(months=-2)).strftime(DEFAULT_SERVER_DATE_FORMAT)
+ groupby_end = (month_begin + relativedelta.relativedelta(months=2)).strftime(DEFAULT_SERVER_DATE_FORMAT)
for id in ids:
created_domain = [
('picking_type_id', '=', id),
- ('state', 'not in', ['done', 'cancel']),
+ ('state', '=', 'done'),
('date', '>=', groupby_begin),
('date', '<', groupby_end),
]
- res[id] = self.__get_bar_values(cr, uid, obj, created_domain, ['date'], 'picking_type_id_count', 'date', context=context)
+ res[id] = self.__get_bar_values(cr, uid, obj, created_domain, ['date','picking_type_id'], 'picking_type_id_count', ['date','picking_type_id'], context=context)
return res
def _get_picking_count(self, cr, uid, ids, field_names, arg, context=None):
obj = self.pool.get('stock.picking')
domains = {
'count_picking_draft': [('state', '=', 'draft')],
- 'count_picking_waiting': [('state','=','confirmed')],
+ 'count_picking_waiting': [('state','=', 'confirmed')],
'count_picking_ready': [('state','=','assigned')],
'count_picking': [('state','in',('assigned','waiting','confirmed'))],
- 'count_picking_late': [('min_date','<', time.strftime('%Y-%m-%d %H:%M:%S')), ('state','in',('assigned','waiting','confirmed'))],
- 'count_picking_backorders': [('backorder_id','<>', False), ('state','!=','done')],
+ 'count_picking_late': [('min_date','<', time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)), ('state','in',('assigned','waiting','confirmed'))],
+ 'count_picking_backorders': [('backorder_id','!=', False), ('state','in',('confirmed', 'assigned', 'waiting'))],
}
result = {}
for field in domains:
for tid in ids:
if result[tid]['count_picking']:
result[tid]['rate_picking_late'] = result[tid]['count_picking_late'] *100 / result[tid]['count_picking']
- result[tid]['rate_picking_backorders'] = result[tid]['count_picking_backorders'] *100 / (result[tid]['count_picking'] + result[tid]['count_picking_draft'])
+ result[tid]['rate_picking_backorders'] = result[tid]['count_picking_backorders'] *100 / result[tid]['count_picking']
else:
result[tid]['rate_picking_late'] = 0
result[tid]['rate_picking_backorders'] = 0
_columns = {
'name': fields.char('Name', translate=True, required=True),
'complete_name': fields.function(_get_name, type='char', string='Name'),
- 'pack': fields.boolean('Prefill Pack Operations', help='This picking type needs packing interface'),
'auto_force_assign': fields.boolean('Automatic Availability', help='This picking type does\'t need to check for the availability in source location.'),
'color': fields.integer('Color'),
- 'delivery': fields.boolean('Print delivery'),
+ 'sequence': fields.integer('Sequence', help="Used to order the 'All Operations' kanban view"),
'sequence_id': fields.many2one('ir.sequence', 'Reference Sequence', required=True),
'default_location_src_id': fields.many2one('stock.location', 'Default Source Location'),
'default_location_dest_id': fields.many2one('stock.location', 'Default Destination Location'),
- #TODO: change field name to "code" as it's not a many2one anymore
- 'code': fields.selection([('incoming', 'Suppliers'), ('outgoing', 'Customers'), ('internal', 'Internal')], 'Picking type code', required=True),
+ 'code': fields.selection([('incoming', 'Suppliers'), ('outgoing', 'Customers'), ('internal', 'Internal')], 'Type of Operation', required=True),
'return_picking_type_id': fields.many2one('stock.picking.type', 'Picking Type for Returns'),
'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', ondelete='cascade'),
'active': fields.boolean('Active'),
# Statistics for the kanban view
- 'weekly_picking': fields.function(_get_picking_data,
+ 'monthly_picking': fields.function(_get_monthly_pickings,
+ type='string',
+ string='Done Pickings per Month'),
+ 'last_done_picking': fields.function(_get_tristate_values,
type='string',
- string='Scheduled pickings per week'),
+ string='Last 10 Done Pickings'),
'count_picking_draft': fields.function(_get_picking_count,
type='integer', multi='_get_picking_count'),