diff --git a/addons/sale/sale.py b/addons/sale/sale.py
index 7d1f82a056a..45374eee18a 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)
@@ -668,6 +669,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
@@ -680,34 +686,38 @@ 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)
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 (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 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
vals = self._prepare_order_line_procurement(cr, uid, order, line, group_id=group_id, context=context)
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
+ #note that the workflow normally ensure proc_ids isn't an empty list
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 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'
- val['shipped'] = False
+ 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/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
+
+
+
+
+
diff --git a/addons/stock/procurement.py b/addons/stock/procurement.py
index 2668520eb57..24b6aa76909 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,
@@ -212,23 +221,33 @@ 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_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:
+ 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. 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.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)