[FIX] a lot a fixes or usability improvements. Includes a branch of jco as well with other fixes

bzr revid: qdp-launchpad@openerp.com-20140416145313-eiadz94h9wolhb9b
This commit is contained in:
Quentin (OpenERP) 2014-04-16 16:53:13 +02:00
commit c41941d978
11 changed files with 270 additions and 85 deletions

View File

@ -90,6 +90,7 @@ Dashboard / Reports for Warehouse Management will include:
'views/report_stockinventory.xml',
],
'test': [
'test/wiseoperator.yml',
'test/inventory.yml',
'test/move.yml',
'test/procrule.yml',

View File

@ -342,6 +342,7 @@ class procurement_order(osv.osv):
'origin': orderpoint.name,
'warehouse_id': orderpoint.warehouse_id.id,
'orderpoint_id': orderpoint.id,
'group_id': orderpoint.group_id.id,
}
def _product_virtual_get(self, cr, uid, order_point):

View File

@ -20,13 +20,39 @@
##############################################################################
from openerp.osv import fields, osv
from openerp.tools.translate import _
class res_company(osv.osv):
_inherit = "res.company"
_columns = {
'propagation_minimum_delta': fields.integer('Minimum Delta for Propagation of a Date Change on moves linked together'),
'internal_transit_location_id': fields.many2one('stock.location', 'Internal Transit Location', help="Technical field used for resupply routes between warehouses that belong to this company", on_delete="restrict"),
}
def create_transit_location(self, cr, uid, company_id, company_name, context=None):
'''Create a transit location with company_id being the given company_id. This is needed
in case of resuply routes between warehouses belonging to the same company, because
we don't want to create accounting entries at that time.
'''
data_obj = self.pool.get('ir.model.data')
try:
parent_loc = data_obj.get_object_reference(cr, uid, 'stock', 'stock_location_locations')[1]
except:
parent_loc = False
location_vals = {
'name': _('%s: Transit Location') % company_name,
'usage': 'transit',
'company_id': company_id,
'location_id': parent_loc,
}
location_id = self.pool.get('stock.location').create(cr, uid, location_vals, context=context)
self.write(cr, uid, [company_id], {'internal_transit_location_id': location_id}, context=context)
def create(self, cr, uid, vals, context=None):
company_id = super(res_company, self).create(cr, uid, vals, context=context)
self.create_transit_location(cr, uid, company_id, vals['name'], context=context)
return company_id
_defaults = {
'propagation_minimum_delta': 1,
}

View File

@ -102,7 +102,7 @@ class stock_location(osv.osv):
_columns = {
'name': fields.char('Location Name', size=64, required=True, translate=True),
'active': fields.boolean('Active', help="By unchecking the active field, you may hide a location without deleting it."),
'usage': fields.selection([('supplier', 'Supplier Location'), ('view', 'View'), ('internal', 'Internal Location'), ('customer', 'Customer Location'), ('inventory', 'Inventory'), ('procurement', 'Procurement'), ('production', 'Production'), ('transit', 'Transit Location for Inter-Companies Transfers')], 'Location Type', required=True,
'usage': fields.selection([('supplier', 'Supplier Location'), ('view', 'View'), ('internal', 'Internal Location'), ('customer', 'Customer Location'), ('inventory', 'Inventory'), ('procurement', 'Procurement'), ('production', 'Production'), ('transit', 'Transit Location')], 'Location Type', required=True,
help="""* Supplier Location: Virtual location representing the source location for products coming from your suppliers
\n* View: Virtual location used to create a hierarchical structures for your warehouse, aggregating its child locations ; can't directly contain products
\n* Internal Location: Physical locations inside your own warehouses,
@ -110,6 +110,7 @@ class stock_location(osv.osv):
\n* Inventory: Virtual location serving as counterpart for inventory operations used to correct stock levels (Physical inventories)
\n* Procurement: Virtual location serving as temporary counterpart for procurement operations when the source (supplier or production) is not known yet. This location should be empty when the procurement scheduler has finished running.
\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",
@ -127,7 +128,7 @@ class stock_location(osv.osv):
'parent_left': fields.integer('Left Parent', select=1),
'parent_right': fields.integer('Right Parent', select=1),
'company_id': fields.many2one('res.company', 'Company', select=1, help='Let this field empty if this location is shared between all companies'),
'company_id': fields.many2one('res.company', 'Company', select=1, help='Let this field empty if this location is shared between companies'),
'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."),
@ -343,11 +344,12 @@ class stock_quant(osv.osv):
elif reserved_availability > 0 and not move.partially_available:
self.pool.get('stock.move').write(cr, uid, [move.id], {'partially_available': True}, context=context)
def quants_move(self, cr, uid, quants, move, location_to, lot_id=False, owner_id=False, src_package_id=False, dest_package_id=False, context=None):
def quants_move(self, cr, uid, quants, move, location_to, location_from=False, lot_id=False, owner_id=False, src_package_id=False, dest_package_id=False, context=None):
"""Moves all given stock.quant in the given destination location.
:param quants: list of tuple(browse record(stock.quant) or None, quantity to move)
:param move: browse record (stock.move)
:param location_to: browse record (stock.location) depicting where the quants have to be moved
:param location_from: optional browse record (stock.location) explaining where the quant has to be taken (may differ from the move source location in case a removal strategy applied). This parameter is only used to pass to _quant_create if a negative quant must be created
:param lot_id: ID of the lot that must be set on the quants to move
:param owner_id: ID of the partner that must own the quants to move
:param src_package_id: ID of the package that contains the quants to move
@ -359,7 +361,7 @@ class stock_quant(osv.osv):
for quant, qty in quants:
if not quant:
#If quant is None, we will create a quant to move (and potentially a negative counterpart too)
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=location_to, context=context)
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()
@ -437,13 +439,14 @@ class stock_quant(osv.osv):
return self._quants_get_order(cr, uid, location, product, quantity, domain, order, context=context)
raise osv.except_osv(_('Error!'), _('Removal strategy %s not implemented.' % (removal_strategy,)))
def _quant_create(self, cr, uid, qty, move, lot_id=False, owner_id=False, src_package_id=False, dest_package_id=False, force_location=False, context=None):
def _quant_create(self, cr, uid, qty, move, lot_id=False, owner_id=False, src_package_id=False, dest_package_id=False,
force_location_from=False, force_location_to=False, context=None):
'''Create a quant in the destination location and create a negative quant in the source location if it's an internal location.
'''
if context is None:
context = {}
price_unit = self.pool.get('stock.move').get_price_unit(cr, uid, move, context=context)
location = force_location or move.location_dest_id
location = force_location_to or move.location_dest_id
vals = {
'product_id': move.product_id.id,
'location_id': location.id,
@ -461,7 +464,7 @@ class stock_quant(osv.osv):
#if we were trying to move something from an internal location and reach here (quant creation),
#it means that a negative quant has to be created as well.
negative_vals = vals.copy()
negative_vals['location_id'] = move.location_id.id
negative_vals['location_id'] = force_location_from and force_location_from.id or move.location_id.id
negative_vals['qty'] = -qty
negative_vals['cost'] = price_unit
negative_vals['negative_move_id'] = move.id
@ -551,7 +554,9 @@ class stock_quant(osv.osv):
self.pool.get('stock.picking').write(cr, uid, [move.picking_id.id], {'recompute_pack_op': True}, context=context)
if move.partially_available:
self.pool.get("stock.move").write(cr, uid, [move.id], {'partially_available': False}, context=context)
return self.write(cr, SUPERUSER_ID, related_quants, {'reservation_id': False}, context=context)
self.write(cr, SUPERUSER_ID, related_quants, {'reservation_id': False}, context=context)
for quant in move.reserved_quant_ids:
self._quant_reconcile_negative(cr, uid, quant, move, context=context)
def _quants_get_order(self, cr, uid, location, product, quantity, domain=[], orderby='in_date', context=None):
''' Implementation of removal strategies
@ -780,7 +785,7 @@ class stock_picking(osv.osv):
_defaults = {
'name': lambda self, cr, uid, context: '/',
'state': 'draft',
'move_type': 'one',
'move_type': 'direct',
'priority': '1', # normal
'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),
@ -803,16 +808,17 @@ class stock_picking(osv.osv):
default['date_done'] = False
return super(stock_picking, self).copy(cr, uid, id, default, context)
def do_print_picking(self, cr, uid, ids, context=None):
'''This function prints the picking list'''
context = context or {}
context['active_ids'] = ids
return self.pool.get("report").get_action(cr, uid, ids, 'stock.report_picking', context=context)
def action_confirm(self, cr, uid, ids, context=None):
todo = []
todo_force_assign = []
for picking in self.browse(cr, uid, ids, context=context):
if picking.picking_type_id.auto_force_assign:
if picking.location_id.usage in ('supplier', 'inventory', 'production'):
todo_force_assign.append(picking.id)
for r in picking.move_lines:
if r.state == 'draft':
@ -981,7 +987,7 @@ class stock_picking(osv.osv):
else:
location = self.pool.get('stock.location').get_putaway_strategy(cr, uid, picking.location_dest_id, product, context=context)
product_putaway_strats[product.id] = location
return location or picking.picking_type_id.default_location_dest_id.id or picking.location_dest_id.id
return location or picking.location_dest_id.id
pack_obj = self.pool.get("stock.quant.package")
quant_obj = self.pool.get("stock.quant")
@ -1026,7 +1032,7 @@ class stock_picking(osv.osv):
if qty <= 0:
continue
suggested_location_id = _picking_putaway_apply(product)
key = (product.id, False, False, False, picking.picking_type_id.default_location_src_id.id or picking.location_id.id, suggested_location_id)
key = (product.id, False, False, False, picking.location_id.id, suggested_location_id)
if qtys_grouped.get(key):
qtys_grouped[key] += qty
else:
@ -1675,7 +1681,6 @@ class stock_move(osv.osv):
'partner_id': _default_destination_address,
'state': 'draft',
'priority': '1',
'product_qty': 1.0,
'product_uom_qty': 1.0,
'scrapped': False,
'date': fields.datetime.now,
@ -2020,8 +2025,8 @@ class stock_move(osv.osv):
""" Changes the state to assigned.
@return: True
"""
#check putaway method
return self.write(cr, uid, ids, {'state': 'assigned'})
#when a MTO move availability is forced, it shouldn't be treated anymore as a MTO so we break the link with ancestors (to avoid further problems in re-reservation)
return self.write(cr, uid, ids, {'state': 'assigned', 'procure_method': 'make_to_stock', 'move_orig_ids': [(6, 0, [])]}, context=context)
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.
@ -2048,7 +2053,7 @@ class stock_move(osv.osv):
for move in self.browse(cr, uid, ids, context=context):
if move.state not in ('confirmed', 'waiting', 'assigned'):
continue
if move.picking_type_id and move.picking_type_id.auto_force_assign:
if move.location_id.usage in ('supplier', 'inventory', 'production'):
to_assign_moves.append(move.id)
#in case the move is returned, we want to try to find quants before forcing the assignment
if not move.origin_returned_move_id:
@ -2063,14 +2068,12 @@ class stock_move(osv.osv):
main_domain[move.id] = [('reservation_id', '=', False), ('qty', '>', 0)]
#if the move is preceeded, restrict the choice of quants in the ones moved previously in original move
move_orig_ids = []
move2 = move
while move2:
move_orig_ids += [x.id for x in move2.move_orig_ids]
#loop on the split_from to find the ancestor of split moves only if the move has not direct ancestor (priority goes to them)
move2 = not move2.move_orig_ids and move2.split_from or False
if move_orig_ids:
main_domain[move.id] += [('history_ids', 'in', move_orig_ids)]
ancestors = self.find_move_ancestors(cr, uid, move, context=context)
if move.state == 'waiting' and not ancestors:
#if the waiting move hasn't yet any ancestor (PO/MO not confirmed yet), don't find any quant available in stock
main_domain[move.id] += [('id', '=', False)]
elif ancestors:
main_domain[move.id] += [('history_ids', 'in', ancestors)]
#if the move is returned from another, restrict the choice of quants to the ones that follow the returned move
if move.origin_returned_move_id:
@ -2099,7 +2102,7 @@ class stock_move(osv.osv):
quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, move.product_id, qty, domain=main_domain[move.id], prefered_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)
#force assignation of consumable products and picking type auto_force_assign
#force assignation of consumable products and incoming from supplier/inventory/production
if to_assign_moves:
self.force_assign(cr, uid, to_assign_moves, context=context)
@ -2134,6 +2137,29 @@ class stock_move(osv.osv):
packs |= set([q.package_id.id for q in move.quant_ids if q.package_id and q.qty > 0])
return pack_obj._check_location_constraint(cr, uid, list(packs), context=context)
def find_move_ancestors(self, cr, uid, move, context=None):
'''Find the first level ancestors of given move '''
ancestors = []
move2 = move
while move2:
ancestors += [x.id for x in move2.move_orig_ids]
#loop on the split_from to find the ancestor of split moves only if the move has not direct ancestor (priority goes to them)
move2 = not move2.move_orig_ids and move2.split_from or False
return ancestors
def check_quants_history(self, cr, uid, move, quants, context=None):
#raise an error in the case the move is chained MTO and the selected quants were not properly chained (manual change in pack operations) because:
#1) users must be wanred they break the MTO flow
#2) if left as it, it will create negative quants complex to reconcile as we allow to take quants only from ancestors,
# and we allow reconciliation only of negative quants created by next move when receiving a chained quant.
ancestors = self.find_move_ancestors(cr, uid, move, context=context)
if move.state == 'waiting' and not ancestors:
raise osv.except_osv(_('Error'), _('You cannot the requested operation. As the move is chained you are supposed to take the same products as in the previous step. Please use the values proposed or force the availability of the move to break the link and process it as you like.'))
if ancestors:
for q, dummy in quants:
if not q or not (set(ancestors) & set([m.id for m in q.history_ids])):
raise osv.except_osv(_('Error'), _('You cannot the requested operation. As the move is chained you are supposed to take the same products as in the previous step. Please use the values proposed or force the availability of the move to break the link and process it as you like.'))
def action_done(self, cr, uid, ids, context=None):
""" Process completly the moves given as ids and if all moves are done, it will finish the picking.
"""
@ -2152,11 +2178,11 @@ class stock_move(osv.osv):
move_qty[move.id] = move.product_qty
for link in move.linked_move_operation_ids:
operations.add(link.operation_id)
#Sort operations according to entire packages first, then package + lot, package only, lot only
operations = list(operations)
operations.sort(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))
operations.sort(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))
for ops in operations:
if ops.picking_id:
pickings.add(ops.picking_id.id)
@ -2164,15 +2190,12 @@ class stock_move(osv.osv):
for record in ops.linked_move_operation_ids:
move = record.move_id
self.check_tracking(cr, uid, move, ops.package_id.id or ops.lot_id.id, context=context)
if record.reserved_quant_id:
quants = [(record.reserved_quant_id, record.qty)]
else:
prefered_domain = [('reservation_id', '=', move.id)]
fallback_domain = [('reservation_id', '=', False)]
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, ops.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)
prefered_domain = [('reservation_id', '=', move.id)]
fallback_domain = [('reservation_id', '=', False)]
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, ops.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)
self.check_quants_history(cr, uid, move, quants, context=context)
if ops.result_package_id.id:
#if a result package is given, all quants go there
quant_dest_package_id = ops.result_package_id.id
@ -2182,20 +2205,21 @@ class stock_move(osv.osv):
else:
#otherwise we keep the current pack of the quant, which may mean None
quant_dest_package_id = ops.package_id.id
quant_obj.quants_move(cr, uid, quants, move, ops.location_dest_id, lot_id=ops.lot_id.id, owner_id=ops.owner_id.id, src_package_id=ops.package_id.id, dest_package_id=quant_dest_package_id, context=context)
quant_obj.quants_move(cr, uid, quants, move, ops.location_dest_id, location_from=ops.location_id, lot_id=ops.lot_id.id, owner_id=ops.owner_id.id, src_package_id=ops.package_id.id, dest_package_id=quant_dest_package_id, context=context)
# 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)
move_qty[move.id] -= record.qty
#Check for remaining qtys and unreserve/check move_dest_id in
#Check for remaining qtys and unreserve/check move_dest_id in
for move in self.browse(cr, uid, ids, context=context):
if move_qty[move.id] > 0: #(=In case no pack operations in picking)
if move_qty[move.id] > 0: # (=In case no pack operations in picking)
main_domain = [('qty', '>', 0)]
prefered_domain = [('reservation_id', '=', move.id)]
fallback_domain = [('reservation_id', '=', False)]
self.check_tracking(cr, uid, move, move.restrict_lot_id.id, context=context)
qty = move_qty[move.id]
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)
self.check_quants_history(cr, uid, move, quants, context=context)
quant_obj.quants_move(cr, uid, quants, move, move.location_dest_id, 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)
@ -2209,10 +2233,10 @@ class stock_move(osv.osv):
self.action_assign(cr, uid, [move.move_dest_id.id], context=context)
if move.procurement_id:
procurement_ids.append(move.procurement_id.id)
# Check the packages have been placed in the correct locations
self._check_package_from_moves(cr, uid, ids, context=context)
# Apply on picking
#set the move as done
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
@ -2718,15 +2742,15 @@ class stock_warehouse(osv.osv):
resupply_wh_ids = list(resupply_wh_ids)
return {'value': {'resupply_wh_ids': resupply_wh_ids}}
def _get_inter_wh_location(self, cr, uid, warehouse, context=None):
''' returns a tuple made of the browse record of customer location and the browse record of supplier location'''
def _get_external_transit_location(self, cr, uid, warehouse, context=None):
''' returns browse record of inter company transit location, if found'''
data_obj = self.pool.get('ir.model.data')
location_obj = self.pool.get('stock.location')
try:
inter_wh_loc = data_obj.get_object_reference(cr, uid, 'stock', 'stock_location_inter_wh')[1]
except:
inter_wh_loc = False
return inter_wh_loc
return False
return location_obj.browse(cr, uid, inter_wh_loc, context=context)
def _get_inter_wh_route(self, cr, uid, warehouse, wh, context=None):
return {
@ -2739,23 +2763,23 @@ class stock_warehouse(osv.osv):
}
def _create_resupply_routes(self, cr, uid, warehouse, supplier_warehouses, default_resupply_wh, context=None):
location_obj = self.pool.get('stock.location')
route_obj = self.pool.get('stock.location.route')
pull_obj = self.pool.get('procurement.rule')
#create route selectable on the product to resupply the warehouse from another one
inter_wh_location_id = self._get_inter_wh_location(cr, uid, warehouse, context=context)
if inter_wh_location_id:
input_loc = warehouse.wh_input_stock_loc_id
if warehouse.reception_steps == 'one_step':
input_loc = warehouse.lot_stock_id
inter_wh_location = location_obj.browse(cr, uid, inter_wh_location_id, context=context)
for wh in supplier_warehouses:
external_transit_location = self._get_external_transit_location(cr, uid, warehouse, context=context)
internal_transit_location = warehouse.company_id.internal_transit_location_id
input_loc = warehouse.wh_input_stock_loc_id
if warehouse.reception_steps == 'one_step':
input_loc = warehouse.lot_stock_id
for wh in supplier_warehouses:
transit_location = wh.company_id.id == warehouse.company_id.id and internal_transit_location or external_transit_location
if transit_location:
output_loc = wh.wh_output_stock_loc_id
if wh.delivery_steps == 'ship_only':
output_loc = wh.lot_stock_id
inter_wh_route_vals = self._get_inter_wh_route(cr, uid, warehouse, wh, context=context)
inter_wh_route_id = route_obj.create(cr, uid, vals=inter_wh_route_vals, context=context)
values = [(output_loc, inter_wh_location, wh.out_type_id.id, wh), (inter_wh_location, input_loc, warehouse.in_type_id.id, warehouse)]
values = [(output_loc, transit_location, wh.out_type_id.id, wh), (transit_location, input_loc, warehouse.in_type_id.id, warehouse)]
pull_rules_list = self._get_supply_pull_rules(cr, uid, warehouse, values, inter_wh_route_id, context=context)
for pull_rule in pull_rules_list:
pull_obj.create(cr, uid, vals=pull_rule, context=context)
@ -3098,7 +3122,6 @@ class stock_warehouse(osv.osv):
'name': _('Receptions'),
'warehouse_id': new_id,
'code': 'incoming',
'auto_force_assign': True,
'sequence_id': in_seq_id,
'default_location_src_id': supplier_loc.id,
'default_location_dest_id': input_loc.id,
@ -3568,6 +3591,8 @@ class stock_package(osv.osv):
default = {}
if not default.get('name'):
default['name'] = self.pool.get('ir.sequence').get(cr, uid, 'stock.quant.package') or _('Unknown Pack')
default['quant_ids'] = []
default['children_ids'] = []
return super(stock_package, self).copy(cr, uid, id, default, context=context)
def copy_pack(self, cr, uid, id, default_pack_values=None, default=None, context=None):
@ -3875,7 +3900,8 @@ class stock_warehouse_orderpoint(osv.osv):
'qty_multiple': fields.integer('Qty Multiple', required=True,
help="The procurement quantity will be rounded up to this multiple."),
'procurement_ids': fields.one2many('procurement.order', 'orderpoint_id', 'Created Procurements'),
'company_id': fields.many2one('res.company', 'Company', required=True)
'group_id': fields.many2one('procurement.group', 'Procurement Group', help="Moves created through this orderpoint will be put in this procurement group. If none is given, the moves generated by procurement rules will be grouped into one big picking."),
'company_id': fields.many2one('res.company', 'Company', required=True),
}
_defaults = {
'active': lambda *a: 1,
@ -3926,13 +3952,15 @@ class stock_warehouse_orderpoint(osv.osv):
return {'value': v, 'domain': d}
return {'domain': {'product_uom': []}}
def copy(self, cr, uid, id, default=None, context=None):
def copy_data(self, cr, uid, id, default=None, context=None):
if not default:
default = {}
default.update({
'name': self.pool.get('ir.sequence').get(cr, uid, 'stock.orderpoint') or '',
'procurement_ids': [],
'group_id': False
})
return super(stock_warehouse_orderpoint, self).copy(cr, uid, id, default, context=context)
return super(stock_warehouse_orderpoint, self).copy_data(cr, uid, id, default, context=context)
class stock_picking_type(osv.osv):
@ -3941,9 +3969,8 @@ class stock_picking_type(osv.osv):
_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'
return {'type': 'ir.actions.act_url', 'url':final_url, 'target': 'self',}
final_url = "/barcode/web/#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):
picking_obj = self.pool.get('stock.picking')
@ -4059,7 +4086,6 @@ class stock_picking_type(osv.osv):
_columns = {
'name': fields.char('Picking Type Name', translate=True, required=True),
'complete_name': fields.function(_get_name, type='char', string='Name'),
'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'),
'sequence': fields.integer('Sequence', help="Used to order the 'All Operations' kanban view"),
'sequence_id': fields.many2one('ir.sequence', 'Reference Sequence', required=True),

View File

@ -71,9 +71,10 @@
</record>
<record id="stock_location_inter_wh" model="stock.location">
<field name="name">Inter Warehouse</field>
<field name="name">Inter Company Transit</field>
<field name="location_id" ref="stock_location_locations_virtual"/>
<field name="usage">transit</field>
<field name="company_id"></field>
</record>
</data>

View File

@ -24,4 +24,10 @@
#avoid the xml id and the associated resource being dropped by the orm by manually making a hit on it
self._update_dummy(cr, uid, xml_record['model'], xml_record['module'], xml_record['name'])
-
!python {model: res.company}: |
#create the transit location for each company existing
company_ids = self.search(cr, uid, [('internal_transit_location_id', '=', False)], context=context)
for company in self.browse(cr, uid, company_ids, context=context):
self.create_transit_location(cr, uid, company.id, company.name, context=context)

View File

@ -674,7 +674,7 @@
<header>
<button name="action_confirm" states="draft" string="Mark as Todo" type="object" class="oe_highlight" groups="base.group_user"/>
<button name="action_assign" states="confirmed,partially_available" string="Check Availability" type="object" class="oe_highlight" groups="base.group_user"/>
<button name="force_assign" states="confirmed,partially_available" string="Force Availability" type="object" class="oe_highlight" groups="base.group_user"/>
<button name="force_assign" states="confirmed,waiting,partially_available" string="Force Availability" type="object" groups="base.group_user"/>
<button name="do_transfer" states="assigned" string="Transfer" groups="stock.group_stock_user" type="object" class="oe_highlight" attrs="{'invisible': ['|', ('pack_operation_exist', '=', True)]}"/>
<button name="do_partial_open_barcode" string="Enter Transfer Details" groups="stock.group_stock_user" type="object" class="oe_highlight" attrs="{'invisible': ['|',('pack_operation_exist', '=', True),('state','not in',('assigned', 'partially_available'))]}"/>
<button name="open_barcode_interface" string="Open Barcode interface" groups="stock.group_stock_user" type="object" class="oe_highlight" attrs="{'invisible': ['|',('pack_operation_exist', '=', False),('state','not in',('assigned', 'partially_available'))]}"/>
@ -1350,7 +1350,6 @@
<group>
<field name="code" on_change="onchange_picking_code(code)"/>
<field name="return_picking_type_id"/>
<field name="auto_force_assign"/>
</group>
</group>
<separator string="Locations"/>
@ -1533,6 +1532,7 @@
<field name="warehouse_id" on_change="onchange_warehouse_id(warehouse_id)" widget="selection" groups="stock.group_locations"/>
<field name="product_uom" groups="product.group_uom"/>
<field name="location_id" groups="stock.group_locations"/>
<field name="group_id" groups="stock.group_adv_location"/>
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
</group>
</group>

View File

@ -0,0 +1,120 @@
-
Create a new stockable product
-
!record {model: product.product, id: product_wise}:
name: Wise Unit
type: product
categ_id: product.product_category_1
uom_id: product.product_uom_unit
uom_po_id: product.product_uom_unit
-
Create an incoming picking for this product of 10 PCE from suppliers to stock
-
!record {model: stock.picking, id: pick1}:
name: Incoming picking
partner_id: base.res_partner_2
picking_type_id: picking_type_in
move_lines:
- product_id: product_wise
product_uom_qty: 10.00
location_id: stock_location_suppliers
location_dest_id: stock_location_stock
-
Confirm and assign picking and prepare partial
-
!python {model: stock.picking}: |
self.action_confirm(cr, uid, [ref('pick1')], context=context)
self.do_prepare_partial(cr, uid, [ref('pick1')], context=context)
-
Put 6 pieces in shelf1 and 4 pieces in shelf2
-
!python {model: stock.picking}: |
record = self.browse(cr, uid, ref('pick1'), context=context)
stock_pack = self.pool.get('stock.pack.operation')
stock_quant_pack = self.pool.get('stock.quant.package')
package1 = stock_quant_pack.create(cr, uid, {'name': 'Pack 1'}, context=context)
stock_pack.write(cr, uid, record.pack_operation_ids[0].id, {'result_package_id': package1, 'product_qty': 4, 'location_dest_id': ref('stock_location_components')})
new_pack1 = stock_pack.create(cr, uid, {'product_id': ref('product_wise'), 'product_uom_id': ref('product.product_uom_unit'), 'picking_id': ref('pick1'), 'product_qty': 6.0, 'location_id': ref('stock_location_suppliers'), 'location_dest_id': ref('stock_location_14')}, context=context)
-
Transfer the reception
-
!python {model: stock.picking}: |
self.do_transfer(cr, uid, [ref('pick1')], context=context)
-
Check the system created 2 quants
-
!python {model: stock.quant}: |
reco_id = self.search(cr ,uid , [('product_id','=',ref('product_wise'))], context=context)
assert len(reco_id) == 2, "The number of quants created is not correct"
-
Make a delivery order of 5 pieces to the customer
-
!record {model: stock.picking, id: delivery_order1}:
name: outgoing picking
partner_id: base.res_partner_4
picking_type_id: stock.picking_type_out
move_lines:
- product_id: product_wise
product_uom_qty: 5.0
location_id: stock_location_stock
location_dest_id: stock_location_customers
-
Assign and confirm
-
!python {model: stock.picking}: |
self.action_confirm(cr, uid, [ref('delivery_order1')], context=context)
self.action_assign(cr, uid, [ref('delivery_order1')])
self.do_prepare_partial(cr, uid, [ref('delivery_order1')])
-
Make a delivery order of 5 pieces to the customer
-
!record {model: stock.picking, id: delivery_order2}:
name: outgoing picking
partner_id: base.res_partner_4
picking_type_id: stock.picking_type_out
move_lines:
- product_id: product_wise
product_uom_qty: 5.0
location_id: stock_location_stock
location_dest_id: stock_location_customers
-
Assign and confirm
-
!python {model: stock.picking}: |
self.action_confirm(cr, uid, [ref('delivery_order2')], context=context)
self.action_assign(cr, uid, [ref('delivery_order2')])
self.do_prepare_partial(cr, uid, [ref('delivery_order2')])
-
The operator is a wise guy and decides to do the opposite as OpenERP proposes. He uses the products reserved on picking 1 on picking 2 and vice versa
-
!python {model: stock.picking}: |
stock_pack = self.pool.get('stock.pack.operation')
picking1 = self.browse(cr, uid, ref('delivery_order1'))
picking2 = self.browse(cr, uid, ref('delivery_order2'))
pack_ids1 = [x.id for x in picking1.pack_operation_ids]
pack_ids2 = [x.id for x in picking2.pack_operation_ids]
stock_pack.write(cr, uid, pack_ids1, {'picking_id': picking2.id})
stock_pack.write(cr, uid, pack_ids2, {'picking_id': picking1.id})
picking1.refresh()
-
Process this picking
-
!python {model: stock.picking}: |
self.do_transfer(cr, uid, [ref('delivery_order1')], context=context)
-
Check a negative quant was created by this picking
-
!python {model: stock.quant}: |
reco_id = self.search(cr ,uid , [('product_id','=',ref('product_wise')), ('qty', '<', 0.0)], context=context)
assert len(reco_id) > 0, 'This should have created a negative quant'
-
Process the second picking
-
!python {model: stock.picking}: |
self.do_transfer(cr, uid, [ref('delivery_order2')], context=context)
-
Check all quants are in Customers and there are no negative quants anymore
-
!python {model: stock.quant}: |
reco_id = self.search(cr ,uid , [('product_id','=',ref('product_wise'))], context=context)
assert all([x.location_id.id==ref('stock_location_customers') and x.qty > 0.0 for x in self.browse(cr, uid, reco_id)]), "Negative quant or wrong location detected"

View File

@ -104,7 +104,7 @@
</t>
<td>
<span t-if="pack_operation.lot_id">
<img t-att-src="'/report/barcode/Standard39/%s' % pack_operation.lot_id"/>
<img t-att-src="'/report/barcode/Standard39/%s' % pack_operation.lot_id.name"/>
</span>
<span t-if="pack_operation.product_id and not pack_operation.lot_id and pack_operation.product_id.ean13">
<img t-att-src="'/report/barcode/EAN13/%s' % pack_operation.product_id.ean13"/>

View File

@ -84,18 +84,23 @@ class stock_change_product_qty(osv.osv_memory):
inventory_line_obj = self.pool.get('stock.inventory.line')
prod_obj_pool = self.pool.get('product.product')
res_original = prod_obj_pool.browse(cr, uid, rec_id, context=context)
for data in self.browse(cr, uid, ids, context=context):
if data.new_quantity < 0:
raise osv.except_osv(_('Warning!'), _('Quantity cannot be negative.'))
inventory_id = inventory_obj.create(cr , uid, {'name': _('INV: %s') % tools.ustr(res_original.name), 'product_id': rec_id, 'location_id': data.location_id.id, 'lot_id': data.lot_id.id}, context=context)
line_data ={
'inventory_id' : inventory_id,
'product_qty' : data.new_quantity,
'location_id' : data.location_id.id,
'product_id' : rec_id,
'product_uom_id' : res_original.uom_id.id,
'prod_lot_id' : data.lot_id.id
ctx = context.copy()
ctx['location'] = data.location_id.id
ctx['lot_id'] = data.lot_id.id
res_original = prod_obj_pool.browse(cr, uid, rec_id, context=ctx)
inventory_id = inventory_obj.create(cr, uid, {'name': _('INV: %s') % tools.ustr(res_original.name), 'product_id': rec_id, 'location_id': data.location_id.id, 'lot_id': data.lot_id.id}, context=context)
th_qty = res_original.qty_available
line_data = {
'inventory_id': inventory_id,
'product_qty': data.new_quantity,
'location_id': data.location_id.id,
'product_id': rec_id,
'product_uom_id': res_original.uom_id.id,
'th_qty': th_qty,
'prod_lot_id': data.lot_id.id
}
inventory_line_obj.create(cr , uid, line_data, context=context)
inventory_obj.action_done(cr, uid, [inventory_id], context=context)

View File

@ -102,11 +102,9 @@ class stock_quant(osv.osv):
context = {}
location_obj = self.pool.get('stock.location')
location_from = move.location_id
location_to = quants[0].location_id
location_to = quants[0].location_id
company_from = location_obj._location_owner(cr, uid, location_from, context=context)
company_to = location_obj._location_owner(cr, uid, location_to, context=context)
if company_from == company_to:
return False
if move.product_id.valuation != 'real_time':
return False
@ -120,8 +118,9 @@ class stock_quant(osv.osv):
#to make the adjustments when we know the real cost price.
return False
#in case of routes making the link between several warehouse of the same company, the transit location belongs to this company, so we don't need to create accounting entries
# Create Journal Entry for products arriving in the company
if company_to:
if company_to and (move.location_id.usage not in ('internal', 'transit') and move.location_dest_id.usage == 'internal' or company_from != company_to):
ctx = context.copy()
ctx['force_company'] = company_to.id
journal_id, acc_src, acc_dest, acc_valuation = self._get_accounting_data_for_valuation(cr, uid, move, context=ctx)
@ -132,7 +131,7 @@ class stock_quant(osv.osv):
self._create_account_move_line(cr, uid, quants, move, acc_src, acc_valuation, journal_id, context=ctx)
# Create Journal Entry for products leaving the company
if company_from:
if company_from and (move.location_id.usage == 'internal' and move.location_dest_id.usage not in ('internal', 'transit') or company_from != company_to):
ctx = context.copy()
ctx['force_company'] = company_from.id
journal_id, acc_src, acc_dest, acc_valuation = self._get_accounting_data_for_valuation(cr, uid, move, context=ctx)
@ -142,8 +141,8 @@ class stock_quant(osv.osv):
else:
self._create_account_move_line(cr, uid, quants, move, acc_valuation, acc_dest, journal_id, context=ctx)
def _quant_create(self, cr, uid, qty, move, lot_id=False, owner_id=False, src_package_id=False, dest_package_id=False, force_location=False, context=None):
quant = super(stock_quant, self)._quant_create(cr, uid, qty, move, lot_id, owner_id, src_package_id, dest_package_id, force_location, context=context)
def _quant_create(self, cr, uid, qty, move, lot_id=False, owner_id=False, src_package_id=False, dest_package_id=False, force_location_from=False, force_location_to=False, context=None):
quant = super(stock_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=force_location_from, force_location_to=force_location_to, context=context)
if move.product_id.valuation == 'real_time':
self._account_entry_move(cr, uid, [quant], move, context)
return quant