From 4419c65f646586014aaf51e3b3ef965def6ee10f Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Thu, 23 Jan 2014 11:37:03 +0100 Subject: [PATCH 01/15] [IMP] Do not reconcile negative quants when the move is chained (move_dest_id) bzr revid: jco@openerp.com-20140123103703-016ko6m1gfk82c95 --- addons/stock/stock.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 91feb16bca5..fd1ea4d256d 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -367,7 +367,8 @@ class stock_quant(osv.osv): if not quant: break new_quant = self.move_single_quant(cr, uid, quant, location_to, qty, move, context=context) - self._quant_reconcile_negative(cr, uid, quant, context=context) + if not move.move_dest_id: + self._quant_reconcile_negative(cr, uid, quant, context=context) quant = new_quant def quants_get_prefered_domain(self, cr, uid, location, product, qty, domain=None, prefered_domain=False, fallback_domain=False, restrict_lot_id=False, restrict_partner_id=False, context=None): From 8c00eddd04f122b9e8460b4e18e6ab0a1f922b24 Mon Sep 17 00:00:00 2001 From: Cedric Snauwaert Date: Thu, 23 Jan 2014 17:06:38 +0100 Subject: [PATCH 02/15] [FIX]sale order, when in shipping exception, don't cancel procurement, re-run it instead. Also run of procurement should take into account previously done move for quantities bzr revid: csn@openerp.com-20140123160638-4dgq1h1yugt6m6zt --- addons/sale/sale.py | 22 +++++++++++++++------- addons/stock/procurement.py | 26 ++++++++++++++++++++------ 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/addons/sale/sale.py b/addons/sale/sale.py index 05bfee51efd..28027187962 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -676,14 +676,21 @@ class sale_order(osv.osv): for order in self.browse(cr, uid, ids, context=context): proc_ids = [] vals = self._prepare_procurement_group(cr, uid, order, context=context) - group_id = self.pool.get("procurement.group").create(cr, uid, vals, context=context) + if not order.procurement_group_id: + group_id = self.pool.get("procurement.group").create(cr, uid, vals, context=context) + order.write({'procurement_group_id': group_id}, context=context) - order.write({'procurement_group_id': group_id}, context=context) + fixed = True for line in order.order_line: - #cancel existing procurements if any (possible when after a shipping exception the user choose to recreate), to avoid duplicates + #Try to fix exception procurement (possible when after a shipping exception the user choose to recreate) if line.procurement_ids: - procurement_obj.cancel(cr, uid, [x.id for x in line.procurement_ids if x.state != 'cancel'], context=context) - if sale_line_obj.need_procurement(cr, uid, [line.id], context=context): + #first check them to see if they are in exception or not + procurement_obj.check(cr, uid, [x.id for x in line.procurement_ids if x.state not in ['cancel', 'done']]) + line.refresh() + #run procurement that are in exception + if not procurement_obj.run(cr, uid, [x.id for x in line.procurement_ids if x.state == 'exception'], context=context): + fixed = False + elif sale_line_obj.need_procurement(cr, uid, [line.id], context=context): if (line.state == 'done') or not line.product_id: continue vals = self._prepare_order_line_procurement(cr, uid, order, line, group_id=group_id, context=context) @@ -691,10 +698,11 @@ class sale_order(osv.osv): proc_ids.append(proc_id) #Confirm procurement order such that rules will be applied on it #note that the workflow ensure proc_ids isn't an empty list - procurement_obj.run(cr, uid, proc_ids, context=context) + if proc_ids: + procurement_obj.run(cr, uid, proc_ids, context=context) # FP NOTE: do we need this? isn't it the workflow that should set this val = {} - if order.state == 'shipping_except': + if order.state == 'shipping_except' and fixed: val['state'] = 'progress' val['shipped'] = False diff --git a/addons/stock/procurement.py b/addons/stock/procurement.py index b47908c73bb..8da9e04e29a 100644 --- a/addons/stock/procurement.py +++ b/addons/stock/procurement.py @@ -172,14 +172,23 @@ class procurement_order(osv.osv): group_id = procurement.group_id and procurement.group_id.id or False elif procurement.rule_id.group_propagation_option == 'fixed': group_id = procurement.rule_id.group_id and procurement.rule_id.group_id.id or False + #it is possible that we've already got some move done, so check for the done qty and create + #a new move with the correct qty + already_done_qty = 0 + already_done_qty_uos = 0 + for move in procurement.move_ids: + already_done_qty += move.product_uom_qty if move.state == 'done' else 0 + already_done_qty_uos += move.product_uos_qty if move.state == 'done' else 0 + qty_left = max(procurement.product_qty-already_done_qty, 0) + qty_uos_left = max(procurement.product_uos_qty-already_done_qty_uos, 0) vals = { 'name': procurement.name, 'company_id': procurement.company_id.id, 'product_id': procurement.product_id.id, - 'product_qty': procurement.product_qty, + 'product_qty': qty_left, 'product_uom': procurement.product_uom.id, - 'product_uom_qty': procurement.product_qty, - 'product_uos_qty': (procurement.product_uos and procurement.product_uos_qty) or procurement.product_qty, + 'product_uom_qty': qty_left, + 'product_uos_qty': (procurement.product_uos and qty_uos_left) or qty_left, 'product_uos': (procurement.product_uos and procurement.product_uos.id) or procurement.product_uom.id, 'partner_id': procurement.group_id and procurement.group_id.partner_id and procurement.group_id.partner_id.id or False, 'location_id': procurement.rule_id.location_src_id.id, @@ -215,20 +224,25 @@ class procurement_order(osv.osv): if procurement.rule_id and procurement.rule_id.action == 'move': done_test_list = [] done_cancel_test_list = [] + qty_done = 0 for move in procurement.move_ids: done_test_list.append(move.state == 'done') done_cancel_test_list.append(move.state in ('done', 'cancel')) + qty_done += move.product_uom_qty if move.state == 'done' else 0 at_least_one_done = any(done_test_list) all_done_or_cancel = all(done_cancel_test_list) if not all_done_or_cancel: return False - elif at_least_one_done and all_done_or_cancel: + elif at_least_one_done and all_done_or_cancel and procurement.product_qty == qty_done: return True + elif at_least_one_done: + #some move cancelled and some validated + self.message_post(cr, uid, [procurement.id], body=_('Some stock moves have been cancelled for this procurement.'), context=context) else: #all move are cancelled - self.write(cr, uid, [procurement.id], {'state': 'exception'}, context=context) self.message_post(cr, uid, [procurement.id], body=_('All stock moves have been cancelled for this procurement.'), context=context) - return False + self.write(cr, uid, [procurement.id], {'state': 'exception'}, context=context) + return False return super(procurement_order, self)._check(cr, uid, procurement, context) From 1e662896fe4ea739ca744ea04c3062275de36433 Mon Sep 17 00:00:00 2001 From: Cedric Snauwaert Date: Thu, 23 Jan 2014 17:21:41 +0100 Subject: [PATCH 03/15] [FIX]when copy sale_order, don't copy procurement_group_id bzr revid: csn@openerp.com-20140123162141-rjd2vyml1d65ayn3 --- addons/sale/sale.py | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/sale/sale.py b/addons/sale/sale.py index 28027187962..865a7e81f5d 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -49,6 +49,7 @@ class sale_order(osv.osv): 'date_confirm': False, 'client_order_ref': '', 'name': self.pool.get('ir.sequence').get(cr, uid, 'sale.order'), + 'procurement_group_id': False, }) return super(sale_order, self).copy(cr, uid, id, default, context=context) From 08cf92d5a8563cda711b47a73f390e2deaf936d6 Mon Sep 17 00:00:00 2001 From: Cedric Snauwaert Date: Fri, 24 Jan 2014 10:27:58 +0100 Subject: [PATCH 04/15] [FIX]sale order workflow: fix problem when clicking on ignore delivery exception bzr revid: csn@openerp.com-20140124092758-tur7cs4d6br30x48 --- addons/sale/sale.py | 5 +++++ addons/sale/sale_workflow.xml | 16 ++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/addons/sale/sale.py b/addons/sale/sale.py index 865a7e81f5d..b7b465735c3 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -665,6 +665,11 @@ class sale_order(osv.osv): res.append(sale_line_obj.need_procurement(cr, uid, [line.id for line in order.order_line], context=context)) return any(res) + def action_ignore_delivery_exception(self, cr, uid, ids, context=None): + for sale_order in self.browse(cr, uid, ids, context=context): + self.write(cr, uid, ids, {'state': 'progress' if sale_order.invoice_exists else 'manual'}, context=context) + return True + def action_ship_create(self, cr, uid, ids, context=None): """Create the required procurements to supply sales order lines, also connecting the procurements to appropriate stock moves in order to bring the goods to the diff --git a/addons/sale/sale_workflow.xml b/addons/sale/sale_workflow.xml index b8e29ac6b3c..831a0a9d12c 100644 --- a/addons/sale/sale_workflow.xml +++ b/addons/sale/sale_workflow.xml @@ -187,6 +187,13 @@ function action_ship_create() + + + + ship_ignore + function + action_ignore_delivery_exception() + @@ -272,11 +279,16 @@ ship_recreate - + - + ship_corrected + + + + + From 7ae921ebb7017aaefb1920b04d3f33f4960f16f3 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Fri, 31 Jan 2014 11:42:06 +0100 Subject: [PATCH 05/15] [IMP] Keep move on negative quant that caused it and take into account lots, move_dest, ... when reconciling negative quants bzr revid: jco@openerp.com-20140131104206-q09dbwxh05p19xi6 --- addons/stock/stock.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index fd1ea4d256d..a9202970310 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -241,7 +241,9 @@ class stock_quant(osv.osv): # 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'), + 'negative_move_id': fields.many2one('stock.move', 'Move Negative Quant', help='If this is a negative quant, this will be the move that caused this negative quant. '), + 'negative_dest_location_id': fields.related('negative_move_id', 'location_dest_id', type='many2one', relation='stock.location', string="Negative 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), } @@ -367,8 +369,7 @@ class stock_quant(osv.osv): if not quant: break new_quant = self.move_single_quant(cr, uid, quant, location_to, qty, move, context=context) - if not move.move_dest_id: - self._quant_reconcile_negative(cr, uid, quant, context=context) + self._quant_reconcile_negative(cr, uid, quant, move, context=context) quant = new_quant def quants_get_prefered_domain(self, cr, uid, location, product, qty, domain=None, prefered_domain=False, fallback_domain=False, restrict_lot_id=False, restrict_partner_id=False, context=None): @@ -449,7 +450,7 @@ class stock_quant(osv.osv): negative_vals['location_id'] = move.location_id.id negative_vals['qty'] = -qty negative_vals['cost'] = price_unit - negative_vals['negative_dest_location_id'] = move.location_dest_id.id + negative_vals['negative_move_id'] = move.id negative_vals['package_id'] = src_package_id negative_quant_id = self.create(cr, SUPERUSER_ID, negative_vals, context=context) vals.update({'propagated_from_id': negative_quant_id}) @@ -480,7 +481,7 @@ class stock_quant(osv.osv): path.append((4, move.id)) self.write(cr, SUPERUSER_ID, solved_quant_ids, {'history_ids': path}, context=context) - def _quant_reconcile_negative(self, cr, uid, quant, context=None): + def _quant_reconcile_negative(self, cr, uid, quant, move, context=None): """ When new quant arrive in a location, try to reconcile it with negative quants. If it's possible, apply the cost of the new @@ -490,10 +491,12 @@ class stock_quant(osv.osv): return False solving_quant = quant dom = [('qty', '<', 0)] - dom += [('lot_id', '=', quant.lot_id and quant.lot_id.id or False)] + if quant.lot_id: + dom += [('lot_id', '=', quant.lot_id.id)] dom += [('owner_id', '=', quant.owner_id and quant.owner_id.id or False)] - dom += [('package_id', '=', quant.package_id and quant.package_id.id or False)] - quants = self.quants_get(cr, uid, quant.location_id, quant.product_id, quant.qty, [('qty', '<', '0')], context=context) + if move.move_dest_id: + dom += [('negative_move_id', '=', move.move_dest_id.id)] + quants = self.quants_get(cr, uid, quant.location_id, quant.product_id, quant.qty, dom, context=context) for quant_neg, qty in quants: if not quant_neg: continue From f001e28587d908f6f983cb53f9ab3861ea593640 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Fri, 31 Jan 2014 17:00:43 +0100 Subject: [PATCH 06/15] [FIX] Should not change source location of move_dest_id bzr revid: jco@openerp.com-20140131160043-pkpzw3geqw953k3p --- addons/purchase/purchase.py | 2 -- addons/stock/stock.py | 4 ++++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py index 4303aae9cbd..e56f8ccd16f 100644 --- a/addons/purchase/purchase.py +++ b/addons/purchase/purchase.py @@ -749,8 +749,6 @@ class purchase_order(osv.osv): for vals in self._prepare_order_line_move(cr, uid, order, order_line, picking_id, group_id, context=context): move = stock_move.create(cr, uid, vals, context=context) todo_moves.append(move) - if order_line.move_dest_id: - order_line.move_dest_id.write({'location_id': order.location_id.id}) stock_move.action_confirm(cr, uid, todo_moves) stock_move.force_assign(cr, uid, todo_moves) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index a9202970310..2ff43d86167 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -908,6 +908,9 @@ class stock_picking(osv.osv): for move in picking.move_lines: if move.state != 'assigned': continue + + + #Check which of the reserved quants are entirely in packages (can be in separate method) packages = list(set([x.package_id for x in move.reserved_quant_ids if x.package_id])) done_packages = [] @@ -929,6 +932,7 @@ class stock_picking(osv.osv): done_packages.append(good_pack) done_packages = list(set(done_packages)) + #Create package operations reserved = set([x.id for x in move.reserved_quant_ids]) remaining_qty = move.product_qty From 746830cde3ed311dbbb848dd5a37991fff2310b8 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Tue, 4 Feb 2014 11:32:09 +0100 Subject: [PATCH 07/15] [WIP] Add constraint for check_location and start refactoring on action_done bzr revid: jco@openerp.com-20140204103209-i2chz9n1db1w9oxr --- addons/stock/stock.py | 107 +++++++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 54 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 2ff43d86167..a1a014e4686 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -251,6 +251,10 @@ class stock_quant(osv.osv): '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) @@ -493,7 +497,8 @@ class stock_quant(osv.osv): dom = [('qty', '<', 0)] if quant.lot_id: dom += [('lot_id', '=', quant.lot_id.id)] - dom += [('owner_id', '=', quant.owner_id and quant.owner_id.id or False)] + dom += [('owner_id', '=', quant.owner_id.id)] + dom += [('package_id', '=', quant.package_id.id)] if move.move_dest_id: dom += [('negative_move_id', '=', move.move_dest_id.id)] quants = self.quants_get(cr, uid, quant.location_id, quant.product_id, quant.qty, dom, context=context) @@ -574,6 +579,8 @@ class stock_quant(osv.osv): for record in self.browse(cr, uid, ids, context=context): if record.location_id.usage == 'view': 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)) + if record.package_id and record.package_id.location_id and (record.package_id.location_id.id != record.location_id.id): + raise osv.except_osv(_('Error'), _('You can not put the products in a package of a different location')) return True _constraints = [ @@ -933,6 +940,7 @@ class stock_picking(osv.osv): done_packages = list(set(done_packages)) + #Create package operations reserved = set([x.id for x in move.reserved_quant_ids]) remaining_qty = move.product_qty @@ -971,6 +979,9 @@ class stock_picking(osv.osv): 'product_uom_id': move.product_id.uom_id.id, 'cost': move.product_id.standard_price, }, context=context) + + #To keep it simple, create pack operations according to negative quants if no pack operations + def do_unreserve(self, cr, uid, picking_ids, context=None): """ @@ -1788,70 +1799,58 @@ class stock_move(osv.osv): self.write(cr, uid, [move.move_dest_id.id], {'state': 'confirmed'}) return self.write(cr, uid, ids, {'state': 'cancel', 'move_dest_id': False}) + def action_done(self, cr, uid, ids, context=None): - """ Makes the move done and if all moves are done, it will finish the picking. - It assumes that quants are already assigned to stock moves. - Putaway strategies should be applied - @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") + pack_obj = self.pool.get("stock.quant.package") todo = [move.id for move in self.browse(cr, uid, ids, context=context) if move.state == "draft"] if todo: self.action_confirm(cr, uid, todo, context=context) - pickings = set() procurement_ids = [] + + #Search operations that are linked to the moves + operations = set() + op_links ={} + move_qty={} for move in self.browse(cr, uid, ids, context=context): - if move.picking_id: - pickings.add(move.picking_id.id) - qty = move.product_qty - main_domain = [('qty', '>', 0)] - prefered_domain = [('reservation_id', '=', move.id)] - 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, 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 - #but for operations having only result_package_id, we will create new quants in the final package directly - package_id = record.operation_id.result_package_id.id or False - quant_obj.quants_move(cr, uid, quants, move, lot_id=record.operation_id.lot_id.id, owner_id=record.operation_id.owner_id.id, src_package_id=record.operation_id.package_id.id, dest_package_id=package_id, context=context) - #packaging process - pack_op_obj.process_packaging(cr, uid, record.operation_id, quants, context=context) - 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: - 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, context=context) - #unreserve the quants and make them available for other operations/moves - quant_obj.quants_unreserve(cr, uid, move, context=context) + move_qty = move.product_qty + operations += [x.operation_id.id for x in move.linked_move_operation_ids] + for link in move.linked_move_operation_ids: + operations.add(link.operation_id) + if link.operation_id.id in op_links: + op_links[link.operation_id.id].append(link.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)) + + for ops in operations: + if ops.picking_id: + pickings.add(ops.picking_id.id) + + #Suppose you are moving entire packages / boxes + if ops.package_id and not ops.product_id: + #Move all quants at once + quants = pack_obj.get_content(cr, uid, [ops.package_id.id], context=context) + # Write function to move package at once, adn also apply put away strategy + else: + main_domain = [('qty', '>', 0)] + for record in ops.linked_move_operation_ids: + move = record.move_id + prefered_domain = [('reservation_id', '=', move.id)] + fallback_domain = [('reservation_id', '=', False)] + self.check_tracking(cr, uid, move, ops.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, restrict_lot_id=move.restrict_lot_id.id, restrict_partner_id=move.restrict_partner_id.id, context=context) + + + - #Check moves that were pushed - if move.move_dest_id.state in ('waiting', 'confirmed'): - other_upstream_move_ids = self.search(cr, uid, [('id', '!=', move.id), ('state', 'not in', ['done', 'cancel']), - ('move_dest_id', '=', move.move_dest_id.id)], context=context) - #If no other moves for the move that got pushed: - if not other_upstream_move_ids and move.move_dest_id.state in ('waiting', 'confirmed'): - self.action_assign(cr, uid, [move.move_dest_id.id], context=context) - if move.procurement_id: - 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): context = context or {} @@ -3122,7 +3121,7 @@ class stock_package(osv.osv): return True _constraints = [ - (_check_location, 'Everything inside a package should be in the same location', ['location_id']), + (_check_location, 'Everything inside a package should be in the same location', ['location_id', 'parent_id']), ] def action_print(self, cr, uid, ids, context=None): From 57635ce01d6f34d1604e0eda53cb67f145d6a242 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Tue, 4 Feb 2014 11:57:43 +0100 Subject: [PATCH 08/15] [WIP] Continue action_done and preliminary code for entire packages bzr revid: jco@openerp.com-20140204105743-o4f7qsqkeqdcbhev --- addons/stock/stock.py | 45 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index a1a014e4686..6e50cfffac0 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -1837,6 +1837,10 @@ class stock_move(osv.osv): #Move all quants at once quants = pack_obj.get_content(cr, uid, [ops.package_id.id], context=context) # Write function to move package at once, adn also apply put away strategy + #TODO: Putaway strategy + location_dest_id = ops.linked_move_operation_ids[0].location_dest_id.id + quant_obj.write(cr, uid, quants, {'location_id': location_dest_id}, context=context) + pack_op_obj.process_packaging(cr, uid, ops, quants, context=context) else: main_domain = [('qty', '>', 0)] for record in ops.linked_move_operation_ids: @@ -1847,8 +1851,45 @@ class stock_move(osv.osv): 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, 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 + #but for operations having only result_package_id, we will create new quants in the final package directly + package_id = record.operation_id.result_package_id.id or False + quant_obj.quants_move(cr, uid, quants, move, lot_id=ops.lot_id.id, owner_id=ops.owner_id.id, src_package_id=ops.package_id.id, dest_package_id=package_id, context=context) + #packaging process + pack_op_obj.process_packaging(cr, uid, ops, quants, context=context) + move_qty[move.id] -= record.qty + #Check for remaining qtys + for move in self.browse(cr, uid, ids, context=context): + if move_qty[move.id] > 0: + 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) + quant_obj.quants_move(cr, uid, quants, move, lot_id=move.restrict_lot_id.id, owner_id=move.restrict_partner_id.id, context=context) + quant_obj.quants_unreserve(cr, uid, move, context=context) + + #Check moves that were pushed + if move.move_dest_id.state in ('waiting', 'confirmed'): + other_upstream_move_ids = self.search(cr, uid, [('id', '!=', move.id), ('state', 'not in', ['done', 'cancel']), + ('move_dest_id', '=', move.move_dest_id.id)], context=context) + #If no other moves for the move that got pushed: + if not other_upstream_move_ids and move.move_dest_id.state in ('waiting', 'confirmed'): + self.action_assign(cr, uid, [move.move_dest_id.id], context=context) + if move.procurement_id: + procurement_ids.append(move.procurement_id.id) + + # Apply on picking + 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 From 3e25efa9e9e01e01b9edba622a744ef3ed07a142 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Tue, 4 Feb 2014 12:36:53 +0100 Subject: [PATCH 09/15] [FIX] action_done refactor bzr revid: jco@openerp.com-20140204113653-kbiurxfeyluoy2y4 --- addons/stock/stock.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 6e50cfffac0..795f97449c8 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -576,11 +576,15 @@ class stock_quant(osv.osv): return location and (location.usage == 'internal') and location.company_id or False def _check_location(self, cr, uid, ids, context=None): + pack_obj = self.pool.get("stock.quant.package") for record in self.browse(cr, uid, ids, context=context): if record.location_id.usage == 'view': 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)) - if record.package_id and record.package_id.location_id and (record.package_id.location_id.id != record.location_id.id): - raise osv.except_osv(_('Error'), _('You can not put the products in a package of a different location')) + if record.package_id: + location_id = pack_obj._get_package_info(cr, uid, [record.package_id.id], '', [], context=None)[record.package_id.id]['location_id'] + #and record.package_id.location_id and (record.package_id.location_id.id != record.location_id.id) + if location_id and location_id != record.location_id.id: + raise osv.except_osv(_('Error'), _('You can not put the products in a package in a different location than the package')) return True _constraints = [ @@ -1817,8 +1821,7 @@ class stock_move(osv.osv): op_links ={} move_qty={} for move in self.browse(cr, uid, ids, context=context): - move_qty = move.product_qty - operations += [x.operation_id.id for x in move.linked_move_operation_ids] + move_qty[move.id] = move.product_qty for link in move.linked_move_operation_ids: operations.add(link.operation_id) if link.operation_id.id in op_links: @@ -1838,9 +1841,11 @@ class stock_move(osv.osv): quants = pack_obj.get_content(cr, uid, [ops.package_id.id], context=context) # Write function to move package at once, adn also apply put away strategy #TODO: Putaway strategy - location_dest_id = ops.linked_move_operation_ids[0].location_dest_id.id + location_dest_id = ops.linked_move_operation_ids[0].move_id.location_dest_id.id quant_obj.write(cr, uid, quants, {'location_id': location_dest_id}, context=context) pack_op_obj.process_packaging(cr, uid, ops, quants, context=context) + for record in ops.linked_move_operation_ids: + move_qty[record.move_id.id] -= record.qty else: main_domain = [('qty', '>', 0)] for record in ops.linked_move_operation_ids: @@ -1858,11 +1863,14 @@ class stock_move(osv.osv): package_id = record.operation_id.result_package_id.id or False quant_obj.quants_move(cr, uid, quants, move, lot_id=ops.lot_id.id, owner_id=ops.owner_id.id, src_package_id=ops.package_id.id, dest_package_id=package_id, context=context) #packaging process - pack_op_obj.process_packaging(cr, uid, ops, quants, context=context) + pack_op_obj.process_packaging(cr, uid, ops, [x[0].id for x in quants], context=context) move_qty[move.id] -= record.qty #Check for remaining qtys for move in self.browse(cr, uid, ids, context=context): if move_qty[move.id] > 0: + 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) @@ -3314,11 +3322,11 @@ class stock_pack_operation(osv.osv): a quant already has been with the good package information so we don't consider that case in this method''' quant_obj = self.pool.get("stock.quant") pack_obj = self.pool.get("stock.quant.package") - for quant, qty in quants: + for quant in quants: 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, SUPERUSER_ID, quant.id, {'package_id': operation.result_package_id.id}, context=context) + quant_obj.write(cr, SUPERUSER_ID, quant, {'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) From 4005fb11054bb95d030248358ce8b4602b65f757 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Tue, 4 Feb 2014 16:49:54 +0100 Subject: [PATCH 10/15] [IMP] Use check packages method in action_done instead of the constraints on package and quant bzr revid: jco@openerp.com-20140204154954-x2q7j0vyvgrdpbk2 --- addons/stock/stock.py | 110 ++++++++++++++++++------------------------ 1 file changed, 48 insertions(+), 62 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 795f97449c8..72b0e4536f3 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -580,11 +580,6 @@ class stock_quant(osv.osv): for record in self.browse(cr, uid, ids, context=context): if record.location_id.usage == 'view': 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)) - if record.package_id: - location_id = pack_obj._get_package_info(cr, uid, [record.package_id.id], '', [], context=None)[record.package_id.id]['location_id'] - #and record.package_id.location_id and (record.package_id.location_id.id != record.location_id.id) - if location_id and location_id != record.location_id.id: - raise osv.except_osv(_('Error'), _('You can not put the products in a package in a different location than the package')) return True _constraints = [ @@ -1803,6 +1798,28 @@ class stock_move(osv.osv): self.write(cr, uid, [move.move_dest_id.id], {'state': 'confirmed'}) return self.write(cr, uid, ids, {'state': 'cancel', 'move_dest_id': False}) + def _check_packages(self, cr, uid, ids, context=None): + quant_obj = self.pool.get("stock.quant") + pack_obj = self.pool.get("stock.quant.package") + # Check all quants moved are in packages and check they are moved in the good way + quants = quant_obj.search(cr, uid, [('history_ids', 'in', ids)], context=context) + # Check parent of parents for all those packages + top_packages = set() + for quant in quant_obj.browse(cr, uid, quants, context=context): + top_package = quant.package_id + while top_package.parent_id: + top_package = top_package.parent_id + if top_package: + top_packages.add(top_package) + for pack in top_packages: + quants = pack_obj.get_content(cr, uid, [pack.id], context=context) + location = False + for quant in quant_obj.browse(cr, uid, quants, context=context): + if not location: + location = quant.location_id.id + elif location != quant.location_id.id: + raise osv.except_osv(_('Error'), _('You can not put a product in a package that has products in another location. ')) + def action_done(self, cr, uid, ids, context=None): context = context or {} @@ -1815,59 +1832,43 @@ class stock_move(osv.osv): self.action_confirm(cr, uid, todo, context=context) pickings = set() procurement_ids = [] - #Search operations that are linked to the moves operations = set() - op_links ={} move_qty={} for move in self.browse(cr, uid, ids, context=context): move_qty[move.id] = move.product_qty for link in move.linked_move_operation_ids: operations.add(link.operation_id) - if link.operation_id.id in op_links: - op_links[link.operation_id.id].append(link.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)) + for ops in operations: if ops.picking_id: pickings.add(ops.picking_id.id) - - #Suppose you are moving entire packages / boxes - if ops.package_id and not ops.product_id: - #Move all quants at once - quants = pack_obj.get_content(cr, uid, [ops.package_id.id], context=context) - # Write function to move package at once, adn also apply put away strategy - #TODO: Putaway strategy - location_dest_id = ops.linked_move_operation_ids[0].move_id.location_dest_id.id - quant_obj.write(cr, uid, quants, {'location_id': location_dest_id}, context=context) - pack_op_obj.process_packaging(cr, uid, ops, quants, context=context) - for record in ops.linked_move_operation_ids: - move_qty[record.move_id.id] -= record.qty - else: - main_domain = [('qty', '>', 0)] - for record in ops.linked_move_operation_ids: - move = record.move_id - prefered_domain = [('reservation_id', '=', move.id)] - fallback_domain = [('reservation_id', '=', False)] - self.check_tracking(cr, uid, move, ops.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, 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 - #but for operations having only result_package_id, we will create new quants in the final package directly - package_id = record.operation_id.result_package_id.id or False - quant_obj.quants_move(cr, uid, quants, move, lot_id=ops.lot_id.id, owner_id=ops.owner_id.id, src_package_id=ops.package_id.id, dest_package_id=package_id, context=context) - #packaging process - pack_op_obj.process_packaging(cr, uid, ops, [x[0].id for x in quants], context=context) - move_qty[move.id] -= record.qty - #Check for remaining qtys + main_domain = [('qty', '>', 0)] + for record in ops.linked_move_operation_ids: + move = record.move_id + prefered_domain = [('reservation_id', '=', move.id)] + fallback_domain = [('reservation_id', '=', False)] + self.check_tracking(cr, uid, move, ops.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, 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 + #but for operations having only result_package_id, we will create new quants in the final package directly + package_id = record.operation_id.result_package_id.id or False + quant_obj.quants_move(cr, uid, quants, move, lot_id=ops.lot_id.id, owner_id=ops.owner_id.id, src_package_id=ops.package_id.id, dest_package_id=package_id, context=context) + #packaging process + pack_op_obj.process_packaging(cr, uid, ops, [x[0].id for x in quants if x[0]], context=context) + move_qty[move.id] -= record.qty + #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: + 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)] @@ -1887,6 +1888,8 @@ class stock_move(osv.osv): if move.procurement_id: procurement_ids.append(move.procurement_id.id) + # Check the packages have been placed in the correct locations + self._check_packages(cr, uid, ids, context=context) # Apply on picking 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) @@ -1900,7 +1903,6 @@ class stock_move(osv.osv): return True - def unlink(self, cr, uid, ids, context=None): context = context or {} for move in self.browse(cr, uid, ids, context=context): @@ -3138,9 +3140,9 @@ class stock_package(osv.osv): 'stock.quant': (_get_packages, ['location_id'], 10), 'stock.quant.package': (_get_packages_to_relocate, ['quant_ids', 'children_ids', 'parent_id'], 10), }, readonly=True), - 'quant_ids': fields.one2many('stock.quant', 'package_id', 'Bulk Content'), - 'parent_id': fields.many2one('stock.quant.package', 'Parent Package', help="The package containing this item", ondelete='restrict'), - 'children_ids': fields.one2many('stock.quant.package', 'parent_id', 'Contained Packages'), + 'quant_ids': fields.one2many('stock.quant', 'package_id', 'Bulk Content', readonly=True), + 'parent_id': fields.many2one('stock.quant.package', 'Parent Package', help="The package containing this item", ondelete='restrict', readonly=True), + 'children_ids': fields.one2many('stock.quant.package', 'parent_id', 'Contained Packages', readonly=True), 'company_id': fields.function(_get_package_info, type="many2one", relation='res.company', string='Company', multi="package", store={ 'stock.quant': (_get_packages, ['company_id'], 10), @@ -3155,23 +3157,7 @@ class stock_package(osv.osv): _defaults = { 'name': lambda self, cr, uid, context: self.pool.get('ir.sequence').get(cr, uid, 'stock.quant.package') or _('Unknown Pack') } - def _check_location(self, cr, uid, ids, context=None): - '''checks that all quants in a package are stored in the same location''' - quant_obj = self.pool.get('stock.quant') - for pack in self.browse(cr, uid, ids, context=context): - parent = pack - while parent.parent_id: - parent = parent.parent_id - quant_ids = self.get_content(cr, uid, [parent.id], context=context) - quants = quant_obj.browse(cr, uid, quant_ids, context=context) - location_id = quants and quants[0].location_id.id or False - if not all([quant.location_id.id == location_id for quant in quants]): - return False - return True - _constraints = [ - (_check_location, 'Everything inside a package should be in the same location', ['location_id', 'parent_id']), - ] def action_print(self, cr, uid, ids, context=None): if context is None: From dfa126d47adb32fe454c2c7d2bacbcd4daadae27 Mon Sep 17 00:00:00 2001 From: "Quentin (OpenERP)" Date: Thu, 6 Feb 2014 15:57:41 +0100 Subject: [PATCH 11/15] [REF] refactoring made during code review bzr revid: qdp-launchpad@openerp.com-20140206145741-ee776b9ttcx4r9wb --- addons/stock/stock.py | 32 +++++++++----------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 6bd5e8d57d1..c47e4a47300 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -242,8 +242,8 @@ class stock_quant(osv.osv): # 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_move_id': fields.many2one('stock.move', 'Move Negative Quant', help='If this is a negative quant, this will be the move that caused this negative quant. '), - 'negative_dest_location_id': fields.related('negative_move_id', 'location_dest_id', type='many2one', relation='stock.location', string="Negative Destination Location", + 'negative_move_id': fields.many2one('stock.move', 'Move Negative Quant', help='If this is a negative quant, this will be the move that caused this negative quant.'), + 'negative_dest_location_id': fields.related('negative_move_id', 'location_dest_id', type='many2one', relation='stock.location', string="Negative 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), } @@ -252,10 +252,6 @@ class stock_quant(osv.osv): '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) @@ -582,7 +578,6 @@ class stock_quant(osv.osv): return location and (location.usage == 'internal') and location.company_id or False def _check_location(self, cr, uid, ids, context=None): - pack_obj = self.pool.get("stock.quant.package") for record in self.browse(cr, uid, ids, context=context): if record.location_id.usage == 'view': 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)) @@ -936,9 +931,7 @@ class stock_picking(osv.osv): for move in picking.move_lines: if move.state not in ('assigned', 'confirmed'): continue - - - + #Check which of the reserved quants are entirely in packages (can be in separate method) packages = list(set([x.package_id for x in move.reserved_quant_ids if x.package_id])) done_packages = [] @@ -960,8 +953,6 @@ class stock_picking(osv.osv): done_packages.append(good_pack) done_packages = list(set(done_packages)) - - #Create package operations reserved = set([x.id for x in move.reserved_quant_ids]) remaining_qty = move.product_qty @@ -1003,9 +994,6 @@ class stock_picking(osv.osv): 'product_uom_id': move.product_id.uom_id.id, 'cost': move.product_id.standard_price, }, context=context) - - #To keep it simple, create pack operations according to negative quants if no pack operations - def do_unreserve(self, cr, uid, picking_ids, context=None): """ @@ -1928,9 +1916,10 @@ class stock_move(osv.osv): location = quant.location_id.id elif location != quant.location_id.id: raise osv.except_osv(_('Error'), _('You can not put a product in a package that has products in another location. ')) - 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. + """ context = context or {} picking_obj = self.pool.get("stock.picking") quant_obj = self.pool.get("stock.quant") @@ -1943,17 +1932,16 @@ class stock_move(osv.osv): procurement_ids = [] #Search operations that are linked to the moves operations = set() - move_qty={} + move_qty = {} for move in self.browse(cr, uid, ids, context=context): 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 = 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)) - for ops in operations: if ops.picking_id: pickings.add(ops.picking_id.id) @@ -1962,7 +1950,7 @@ class stock_move(osv.osv): move = record.move_id prefered_domain = [('reservation_id', '=', move.id)] fallback_domain = [('reservation_id', '=', False)] - self.check_tracking(cr, uid, move, ops.lot_id.id, context=context) + self.check_tracking(cr, uid, move, ops.package_id.id or ops.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, restrict_lot_id=move.restrict_lot_id.id, restrict_partner_id=move.restrict_partner_id.id, context=context) @@ -1987,7 +1975,7 @@ class stock_move(osv.osv): 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) - + #Check moves that were pushed if move.move_dest_id.state in ('waiting', 'confirmed'): other_upstream_move_ids = self.search(cr, uid, [('id', '!=', move.id), ('state', 'not in', ['done', 'cancel']), @@ -2012,7 +2000,6 @@ class stock_move(osv.osv): 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): context = context or {} for move in self.browse(cr, uid, ids, context=context): @@ -3271,7 +3258,6 @@ class stock_package(osv.osv): 'name': lambda self, cr, uid, context: self.pool.get('ir.sequence').get(cr, uid, 'stock.quant.package') or _('Unknown Pack') } - def action_print(self, cr, uid, ids, context=None): if context is None: context = {} From 8c0c3dae685ecf5de162ed71db7c224734ff2819 Mon Sep 17 00:00:00 2001 From: "Quentin (OpenERP)" Date: Thu, 6 Feb 2014 16:31:28 +0100 Subject: [PATCH 12/15] [REF] refactoring made during code review bzr revid: qdp-launchpad@openerp.com-20140206153128-r2af5xu0ye2nlrut --- addons/stock/stock.py | 44 ++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index c47e4a47300..d3c89b0c251 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -1895,27 +1895,12 @@ class stock_move(osv.osv): self.write(cr, uid, [move.move_dest_id.id], {'state': 'confirmed'}) return self.write(cr, uid, ids, {'state': 'cancel', 'move_dest_id': False}) - def _check_packages(self, cr, uid, ids, context=None): - quant_obj = self.pool.get("stock.quant") + def _check_package_from_moves(self, cr, uid, ids, context=None): pack_obj = self.pool.get("stock.quant.package") - # Check all quants moved are in packages and check they are moved in the good way - quants = quant_obj.search(cr, uid, [('history_ids', 'in', ids)], context=context) - # Check parent of parents for all those packages - top_packages = set() - for quant in quant_obj.browse(cr, uid, quants, context=context): - top_package = quant.package_id - while top_package.parent_id: - top_package = top_package.parent_id - if top_package: - top_packages.add(top_package) - for pack in top_packages: - quants = pack_obj.get_content(cr, uid, [pack.id], context=context) - location = False - for quant in quant_obj.browse(cr, uid, quants, context=context): - if not location: - location = quant.location_id.id - elif location != quant.location_id.id: - raise osv.except_osv(_('Error'), _('You can not put a product in a package that has products in another location. ')) + packs = set() + for move in self.browse(cr, uid, ids, context=context): + packs |= set([q.package_id.id for q in move.quant_ids if q.package_id]) + return pack_obj._check_location_constraint(cr, uid, list(packs), context=context) 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. @@ -1987,7 +1972,7 @@ class stock_move(osv.osv): procurement_ids.append(move.procurement_id.id) # Check the packages have been placed in the correct locations - self._check_packages(cr, uid, ids, context=context) + self._check_package_from_moves(cr, uid, ids, context=context) # Apply on picking 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) @@ -3258,6 +3243,23 @@ class stock_package(osv.osv): 'name': lambda self, cr, uid, context: self.pool.get('ir.sequence').get(cr, uid, 'stock.quant.package') or _('Unknown Pack') } + def _check_location_constraint(self, cr, uid, ids, context=None): + '''checks that all quants in a package are stored in the same location. This function cannot be used + as a constraint because it needs to be checked on pack operations (they may not call write on the + package) + ''' + quant_obj = self.pool.get('stock.quant') + for pack in self.browse(cr, uid, ids, context=context): + parent = pack + while parent.parent_id: + parent = parent.parent_id + quant_ids = self.get_content(cr, uid, [parent.id], context=context) + quants = quant_obj.browse(cr, uid, quant_ids, context=context) + location_id = quants and quants[0].location_id.id or False + if not all([quant.location_id.id == location_id for quant in quants]): + raise osv.except_osv(_('Error'), _('Everything inside a package should be in the same location')) + return True + def action_print(self, cr, uid, ids, context=None): if context is None: context = {} From 38577e23f4851ad624c0b5b23a1c170a98ce8168 Mon Sep 17 00:00:00 2001 From: "Quentin (OpenERP)" Date: Fri, 7 Feb 2014 09:40:00 +0100 Subject: [PATCH 13/15] [IMP] stock, usability: changed a button label and translate the information on availability of moves into the move uom bzr revid: qdp-launchpad@openerp.com-20140207084000-k7uhl82v9mlmuc92 --- addons/stock/stock.py | 7 +++++-- addons/stock/stock_view.xml | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index d3c89b0c251..4ee6f3a926b 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -1303,23 +1303,26 @@ class stock_move(osv.osv): def _get_string_qty_information(self, cr, uid, ids, field_name, args, context=None): settings_obj = self.pool.get('stock.config.settings') + uom_obj = self.pool.get('product.uom') res = dict.fromkeys(ids, '') for move in self.browse(cr, uid, ids, context=context): if move.state in ('draft', 'done', 'cancel') or move.location_id.usage != 'internal': res[move.id] = _('n/a') continue total_available = min(move.product_qty, move.reserved_availability + move.availability) + total_available = uom_obj._compute_qty(cr, uid, move.product_id.uom_id.id, total_available, move.product_uom.id) info = str(total_available) #look in the settings if we need to display the UoM name or not config_ids = settings_obj.search(cr, uid, [], limit=1, order='id DESC', context=context) if config_ids: stock_settings = settings_obj.browse(cr, uid, config_ids[0], context=context) if stock_settings.group_uom: - info += ' ' + move.product_id.uom_id.name + info += ' ' + move.product_uom.name if move.reserved_availability: if move.reserved_availability != total_available: #some of the available quantity is assigned and some are available but not reserved - info += _(' (%s reserved)') % str(move.reserved_availability) + reserved_available = uom_obj._compute_qty(cr, uid, move.product_id.uom_id.id, move.reserved_availability, move.product_uom.id) + info += _(' (%s reserved)') % str(reserved_available) else: #all available quantity is assigned info += _(' (reserved)') diff --git a/addons/stock/stock_view.xml b/addons/stock/stock_view.xml index 0afe8f8df34..303785da294 100644 --- a/addons/stock/stock_view.xml +++ b/addons/stock/stock_view.xml @@ -754,8 +754,8 @@