From 8c00eddd04f122b9e8460b4e18e6ab0a1f922b24 Mon Sep 17 00:00:00 2001 From: Cedric Snauwaert Date: Thu, 23 Jan 2014 17:06:38 +0100 Subject: [PATCH 1/4] [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 2/4] [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 3/4] [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 f9d3e8a39ed3fcff10fd0c0a947cadf06e5ec6f8 Mon Sep 17 00:00:00 2001 From: "Quentin (OpenERP)" Date: Fri, 7 Feb 2014 12:13:49 +0100 Subject: [PATCH 4/4] [REF] some refactoring, bug fixes made during code review bzr revid: qdp-launchpad@openerp.com-20140207111349-2zjysk3w1p0daeaj --- addons/sale/sale.py | 24 ++++++++++-------------- addons/stock/procurement.py | 15 ++++++++++----- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/addons/sale/sale.py b/addons/sale/sale.py index ee076cebaa7..45374eee18a 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -690,16 +690,14 @@ class sale_order(osv.osv): group_id = self.pool.get("procurement.group").create(cr, uid, vals, context=context) order.write({'procurement_group_id': group_id}, context=context) - fixed = True for line in order.order_line: #Try to fix exception procurement (possible when after a shipping exception the user choose to recreate) if line.procurement_ids: - #first check them to see if they are in exception or not + #first check them to see if they are in exception or not (one of the related moves is cancelled) 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 + #run again procurement that are in exception in order to trigger another move + proc_ids += [x.id for x in line.procurement_ids if x.state == 'exception'] elif sale_line_obj.need_procurement(cr, uid, [line.id], context=context): if (line.state == 'done') or not line.product_id: continue @@ -707,21 +705,19 @@ class sale_order(osv.osv): proc_id = procurement_obj.create(cr, uid, vals, context=context) 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 - 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' and fixed: - val['state'] = 'progress' - val['shipped'] = False + #note that the workflow normally ensure proc_ids isn't an empty list + procurement_obj.run(cr, uid, proc_ids, context=context) + + #if shipping was in exception and the user choose to recreate the delivery order, write the new status of SO + if order.state == 'shipping_except': + val = {'state': 'progress', 'shipped': False} if (order.order_policy == 'manual'): for line in order.order_line: if (not line.invoiced) and (line.state not in ('cancel', 'draft')): val['state'] = 'manual' break - order.write(val) + order.write(val) return True # if mode == 'finished': diff --git a/addons/stock/procurement.py b/addons/stock/procurement.py index 9e4083ab702..24b6aa76909 100644 --- a/addons/stock/procurement.py +++ b/addons/stock/procurement.py @@ -179,8 +179,8 @@ class procurement_order(osv.osv): 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) + 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, @@ -221,23 +221,28 @@ class procurement_order(osv.osv): return super(procurement_order, self)._run(cr, uid, procurement, context) def _check(self, cr, uid, procurement, context=None): + ''' Implement the procurement checking for rules of type 'move'. The procurement will be satisfied only if all related + moves are done/cancel and if the requested quantity is moved. + ''' if procurement.rule_id and procurement.rule_id.action == 'move': + uom_obj = self.pool.get('product.uom') 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 + qty_done += move.product_qty if move.state == 'done' else 0 + qty_done = uom_obj._compute_qty(cr, uid, procurement.product_id.uom_id.id, qty_done, procurement.product_uom.id) 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 and procurement.product_qty == qty_done: + elif 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) + self.message_post(cr, uid, [procurement.id], body=_('Some stock moves have been cancelled for this procurement. Run the procurement again to trigger a move for the remaining quantity or change the procurement quantity to finish it directly'), context=context) else: #all move are cancelled self.message_post(cr, uid, [procurement.id], body=_('All stock moves have been cancelled for this procurement.'), context=context)