Merge lp:~openerp-dev/openobject-addons/trunk-wms-salestockwkf-jco into lp:~openerp-dev/openobject-addons/trunk-wms

Proposed by Josse Colpaert (OpenERP)
Status: Needs review
Proposed branch: lp:~openerp-dev/openobject-addons/trunk-wms-salestockwkf-jco
Merge into: lp:~openerp-dev/openobject-addons/trunk-wms
Diff against target: 331 lines (+105/-85)
5 files modified
sale/sale.py (+13/-51)
sale/sale_view.xml (+1/-0)
sale/sale_workflow.xml (+10/-4)
sale_stock/sale_stock.py (+57/-12)
stock/stock.py (+24/-18)
To merge this branch: bzr merge lp:~openerp-dev/openobject-addons/trunk-wms-salestockwkf-jco
Reviewer Review Type Date Requested Status
qdp (OpenERP) Pending
Review via email: mp+217566@code.launchpad.net

Description of the change

Change sale workflow to work as purchase workflow. The problem is that in the current workflow the sale order will not go to done when we invoice first and do the delivery afterwards.

Made also changes in procurement of cancel / split and last 2 commits make sure that the procurements of the sale order get cancelled also.

To post a comment you must log in.

Unmerged revisions

9702. By Josse Colpaert (OpenERP)

[IMP] When cancelling a sale order, cancel its procurements

9701. By Josse Colpaert (OpenERP)

[IMP] In case of ignoring exception of sale order, cancel procurements

9700. By Josse Colpaert (OpenERP)

[IMP] Adjust workflow not to take into account procurements when having procurements for services and do not throw error trying to cancel or split moves

9699. By Josse Colpaert (OpenERP)

[IMP] Clean test_state

9698. By Josse Colpaert (OpenERP)

[IMP] Do with procurements in sale order the same thing as with moves and purchase orders

9697. By Quentin (OpenERP) <email address hidden>

[REF] stock_account: removal of TODO statements

9696. By Quentin (OpenERP) <email address hidden>

[REF] stock: removal of TODO statements

9695. By Quentin (OpenERP) <email address hidden>

[MERGE] branch of jco fixing shipping exception in mrp + cancellation in chained moves

9694. By Quentin (OpenERP) <email address hidden>

[MERGE] stock_landed_costs: better usability

9693. By Quentin (OpenERP) <email address hidden>

[REF] stock_account: refactoring of do_change_standard_price() + removal of TODO statements

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'sale/sale.py'
--- sale/sale.py 2014-04-16 15:05:51 +0000
+++ sale/sale.py 2014-04-29 09:32:43 +0000
@@ -228,7 +228,7 @@
228 'payment_term': fields.many2one('account.payment.term', 'Payment Term'),228 'payment_term': fields.many2one('account.payment.term', 'Payment Term'),
229 'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position'),229 'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position'),
230 'company_id': fields.many2one('res.company', 'Company'),230 'company_id': fields.many2one('res.company', 'Company'),
231 'procurement_group_id': fields.many2one('procurement.group', 'Procurement group'),231 'procurement_group_id': fields.many2one('procurement.group', 'Procurement group', readonly=True),
232 }232 }
233 _defaults = {233 _defaults = {
234 'date_order': fields.datetime.now,234 'date_order': fields.datetime.now,
@@ -465,10 +465,13 @@
465 result['res_id'] = inv_ids and inv_ids[0] or False465 result['res_id'] = inv_ids and inv_ids[0] or False
466 return result466 return result
467467
468 def test_no_products(self, cr, uid, ids, context=None):
469 for order in self.browse(cr, uid, ids, context=context):
470 if not self.test_no_product(cr, uid, order, context=context):
471 return False
472 return True
473
468 def test_no_product(self, cr, uid, order, context):474 def test_no_product(self, cr, uid, order, context):
469 for line in order.order_line:
470 if line.product_id and (line.product_id.type<>'service'):
471 return False
472 return True475 return True
473476
474 def action_invoice_create(self, cr, uid, ids, grouped=False, states=None, date_invoice = False, context=None):477 def action_invoice_create(self, cr, uid, ids, grouped=False, states=None, date_invoice = False, context=None):
@@ -663,6 +666,7 @@
663 res.append(sale_line_obj.need_procurement(cr, uid, [line.id for line in order.order_line], context=context))666 res.append(sale_line_obj.need_procurement(cr, uid, [line.id for line in order.order_line], context=context))
664 return any(res)667 return any(res)
665668
669
666 def action_ignore_delivery_exception(self, cr, uid, ids, context=None):670 def action_ignore_delivery_exception(self, cr, uid, ids, context=None):
667 for sale_order in self.browse(cr, uid, ids, context=context):671 for sale_order in self.browse(cr, uid, ids, context=context):
668 self.write(cr, uid, ids, {'state': 'progress' if sale_order.invoice_exists else 'manual'}, context=context)672 self.write(cr, uid, ids, {'state': 'progress' if sale_order.invoice_exists else 'manual'}, context=context)
@@ -714,53 +718,11 @@
714 order.write(val)718 order.write(val)
715 return True719 return True
716720
717 # if mode == 'finished':721
718 # returns True if all lines are done, False otherwise722
719 # if mode == 'canceled':723
720 # returns True if there is at least one canceled line, False otherwise724 def action_ship_end(self, cr, uid, ids, context=None):
721 def test_state(self, cr, uid, ids, mode, *args):725 return True
722 assert mode in ('finished', 'canceled'), _("invalid mode for test_state")
723 finished = True
724 canceled = False
725 write_done_ids = []
726 write_cancel_ids = []
727 for order in self.browse(cr, uid, ids, context={}):
728
729 #TODO: Need to rethink what happens when cancelling
730 for line in order.order_line:
731 states = [x.state for x in line.procurement_ids]
732 cancel = states and all([x == 'cancel' for x in states])
733 doneorcancel = all([x in ('done', 'cancel') for x in states])
734 if cancel:
735 canceled = True
736 if line.state != 'exception':
737 write_cancel_ids.append(line.id)
738 if not doneorcancel:
739 finished = False
740 if doneorcancel and not cancel:
741 write_done_ids.append(line.id)
742
743 if write_done_ids:
744 self.pool.get('sale.order.line').write(cr, uid, write_done_ids, {'state': 'done'})
745 if write_cancel_ids:
746 self.pool.get('sale.order.line').write(cr, uid, write_cancel_ids, {'state': 'exception'})
747
748 if mode == 'finished':
749 return finished
750 elif mode == 'canceled':
751 return canceled
752
753
754 def procurement_lines_get(self, cr, uid, ids, *args):
755 res = []
756 for order in self.browse(cr, uid, ids, context={}):
757 for line in order.order_line:
758 res += [x.id for x in line.procurement_ids]
759 return res
760
761
762
763
764726
765# TODO add a field price_unit_uos727# TODO add a field price_unit_uos
766# - update it on change product and unit price728# - update it on change product and unit price
767729
=== modified file 'sale/sale_view.xml'
--- sale/sale_view.xml 2014-04-22 09:28:15 +0000
+++ sale/sale_view.xml 2014-04-29 09:32:43 +0000
@@ -202,6 +202,7 @@
202 <group name="sales_person" groups="base.group_user">202 <group name="sales_person" groups="base.group_user">
203 <field name="user_id" context="{'default_groups_ref': ['base.group_user', 'base.group_partner_manager', 'account.group_account_invoice', 'base.group_sale_salesman_all_leads']}"/>203 <field name="user_id" context="{'default_groups_ref': ['base.group_user', 'base.group_partner_manager', 'account.group_account_invoice', 'base.group_sale_salesman_all_leads']}"/>
204 <field groups="base.group_no_one" name="origin"/>204 <field groups="base.group_no_one" name="origin"/>
205 <field name="procurement_group_id"/>
205 </group>206 </group>
206 <group name="sale_pay">207 <group name="sale_pay">
207 <field name="payment_term" options="{'no_create': True}"/>208 <field name="payment_term" options="{'no_create': True}"/>
208209
=== modified file 'sale/sale_workflow.xml'
--- sale/sale_workflow.xml 2014-01-24 09:27:58 +0000
+++ sale/sale_workflow.xml 2014-04-29 09:32:43 +0000
@@ -198,9 +198,11 @@
198 <record id="act_ship_end" model="workflow.activity">198 <record id="act_ship_end" model="workflow.activity">
199 <field name="wkf_id" ref="sale.wkf_sale"/>199 <field name="wkf_id" ref="sale.wkf_sale"/>
200 <field name="name">ship_end</field>200 <field name="name">ship_end</field>
201 <field name="kind">dummy</field>201 <field name="kind">function</field>
202 <field name="action">action_ship_end()</field>
202 </record>203 </record>
203 204
205
204 <record id="act_ship_cancel" model="workflow.activity">206 <record id="act_ship_cancel" model="workflow.activity">
205 <field name="wkf_id" ref="sale.wkf_sale"/>207 <field name="wkf_id" ref="sale.wkf_sale"/>
206 <field name="name">ship_cancel</field>208 <field name="name">ship_cancel</field>
@@ -262,9 +264,13 @@
262 <record id="trans_ship_ship_end" model="workflow.transition">264 <record id="trans_ship_ship_end" model="workflow.transition">
263 <field name="act_from" ref="act_ship"/>265 <field name="act_from" ref="act_ship"/>
264 <field name="act_to" ref="act_ship_end"/>266 <field name="act_to" ref="act_ship_end"/>
265 <field name="trigger_model">procurement.order</field>267 <field name="signal">ship_end</field>
266 <field name="trigger_expr_id">procurement_lines_get()</field>268 </record>
267 <field name="condition">test_state('finished')</field>269
270 <record id="trans_ship_ship_end2" model="workflow.transition">
271 <field name="act_from" ref="act_ship"/>
272 <field name="act_to" ref="act_ship_end"/>
273 <field name="condition">test_no_products()</field>
268 </record>274 </record>
269 275
270 <record id="trans_ship_ship_except" model="workflow.transition">276 <record id="trans_ship_ship_except" model="workflow.transition">
271277
=== modified file 'sale_stock/sale_stock.py'
--- sale_stock/sale_stock.py 2014-04-25 12:18:10 +0000
+++ sale_stock/sale_stock.py 2014-04-29 09:32:43 +0000
@@ -159,7 +159,6 @@
159 def action_cancel(self, cr, uid, ids, context=None):159 def action_cancel(self, cr, uid, ids, context=None):
160 if context is None:160 if context is None:
161 context = {}161 context = {}
162 sale_order_line_obj = self.pool.get('sale.order.line')
163 proc_obj = self.pool.get('procurement.order')162 proc_obj = self.pool.get('procurement.order')
164 stock_obj = self.pool.get('stock.picking')163 stock_obj = self.pool.get('stock.picking')
165 for sale in self.browse(cr, uid, ids, context=context):164 for sale in self.browse(cr, uid, ids, context=context):
@@ -168,6 +167,10 @@
168 raise osv.except_osv(167 raise osv.except_osv(
169 _('Cannot cancel sales order!'),168 _('Cannot cancel sales order!'),
170 _('You must first cancel all delivery order(s) attached to this sales order.'))169 _('You must first cancel all delivery order(s) attached to this sales order.'))
170 procurements = []
171 for line in sale.order_line:
172 procurements += [x.id for x in line.procurement_ids if x.state not in ('cancel', 'done') and x.product_id.type != 'service']
173 proc_obj.cancel(cr, uid, procurements, context=context)
171 stock_obj.signal_button_cancel(cr, uid, [p.id for p in sale.picking_ids])174 stock_obj.signal_button_cancel(cr, uid, [p.id for p in sale.picking_ids])
172 return super(sale_order, self).action_cancel(cr, uid, ids, context=context)175 return super(sale_order, self).action_cancel(cr, uid, ids, context=context)
173176
@@ -189,10 +192,11 @@
189 res.update({'move_type': order.picking_policy})192 res.update({'move_type': order.picking_policy})
190 return res193 return res
191194
195
192 def action_ship_end(self, cr, uid, ids, context=None):196 def action_ship_end(self, cr, uid, ids, context=None):
193 super(sale_order, self).action_ship_end(cr, uid, ids, context=context)197 super(sale_order, self).action_ship_end(cr, uid, ids, context=context)
194 for order in self.browse(cr, uid, ids, context=context):198 for order in self.browse(cr, uid, ids, context=context):
195 val = {'shipped': True}199 val = {}
196 if order.state == 'shipping_except':200 if order.state == 'shipping_except':
197 val['state'] = 'progress'201 val['state'] = 'progress'
198 if (order.order_policy == 'manual'):202 if (order.order_policy == 'manual'):
@@ -203,8 +207,25 @@
203 res = self.write(cr, uid, [order.id], val)207 res = self.write(cr, uid, [order.id], val)
204 return True208 return True
205209
206210 def test_no_product(self, cr, uid, order, context):
207211 for line in order.order_line:
212 if line.product_id and (line.product_id.type<>'service'):
213 return False
214 return True
215
216 def test_procurements_done(self, cr, uid, ids, context=None):
217 for sale in self.browse(cr, uid, ids, context=context):
218 for line in sale.order_line:
219 if not all([x.state == 'done' for x in line.procurement_ids if x.product_id.type != 'service']):
220 return False
221 return True
222
223 def test_procurements_except(self, cr, uid, ids, context=None):
224 for sale in self.browse(cr, uid, ids, context=context):
225 for line in sale.order_line:
226 if any([x.state in ('exception', 'cancel') and x.move_ids for x in line.procurement_ids]):
227 return True
228 return False
208229
209 def has_stockable_products(self, cr, uid, ids, *args):230 def has_stockable_products(self, cr, uid, ids, *args):
210 for order in self.browse(cr, uid, ids):231 for order in self.browse(cr, uid, ids):
@@ -213,6 +234,19 @@
213 return True234 return True
214 return False235 return False
215236
237 def action_ignore_delivery_exception(self, cr, uid, ids, context=None):
238 """
239 In case of ignoring the exception it should cancel the procurements
240 """
241 proc_obj = self.pool.get("procurement.order")
242 exceptions = []
243 for order in self.browse(cr, uid, ids, context=context):
244 for line in order.order_line:
245 exceptions += [x.id for x in line.procurement_ids if x.state == "exception"]
246 if exceptions:
247 proc_obj.cancel(cr, uid, exceptions, context=context)
248 return super(sale_order, self).action_ignore_delivery_exception(cr, uid, ids, context=context)
249
216250
217class sale_order_line(osv.osv):251class sale_order_line(osv.osv):
218 _inherit = 'sale.order.line'252 _inherit = 'sale.order.line'
@@ -375,17 +409,28 @@
375 res.update({'warning': warning})409 res.update({'warning': warning})
376 return res410 return res
377411
412
413class procurement_order(osv.osv):
414 _inherit = 'procurement.order'
415
416 def write(self, cr, uid, ids, vals, context=None):
417 if isinstance(ids, (int, long)):
418 ids = [ids]
419 res = super(procurement_order, self).write(cr, uid, ids, vals, context=context)
420 from openerp import workflow
421 if vals.get('state') in ['done', 'cancel', 'exception']:
422 for proc in self.browse(cr, uid, ids, context=context):
423 if proc.sale_line_id and proc.sale_line_id.order_id and proc.move_ids:
424 order_id = proc.sale_line_id.order_id.id
425 if self.pool.get('sale.order').test_procurements_done(cr, uid, [order_id], context=context):
426 workflow.trg_validate(uid, 'sale.order', order_id, 'ship_end', cr)
427 if self.pool.get('sale.order').test_procurements_except(cr, uid, [order_id], context=context):
428 workflow.trg_validate(uid, 'sale.order', order_id, 'ship_except', cr)
429 return res
430
378class stock_move(osv.osv):431class stock_move(osv.osv):
379 _inherit = 'stock.move'432 _inherit = 'stock.move'
380433
381 def action_cancel(self, cr, uid, ids, context=None):
382 sale_ids = []
383 for move in self.browse(cr, uid, ids, context=context):
384 if move.procurement_id and move.procurement_id.sale_line_id:
385 sale_ids.append(move.procurement_id.sale_line_id.order_id.id)
386 if sale_ids:
387 self.pool.get('sale.order').signal_ship_except(cr, uid, sale_ids)
388 return super(stock_move, self).action_cancel(cr, uid, ids, context=context)
389434
390 def _create_invoice_line_from_vals(self, cr, uid, move, invoice_line_vals, context=None):435 def _create_invoice_line_from_vals(self, cr, uid, move, invoice_line_vals, context=None):
391 invoice_line_id = self.pool.get('account.invoice.line').create(cr, uid, invoice_line_vals, context=context)436 invoice_line_id = self.pool.get('account.invoice.line').create(cr, uid, invoice_line_vals, context=context)
392437
=== modified file 'stock/stock.py'
--- stock/stock.py 2014-04-28 14:55:36 +0000
+++ stock/stock.py 2014-04-29 09:32:43 +0000
@@ -2139,31 +2139,37 @@
2139 """2139 """
2140 procurement_obj = self.pool.get('procurement.order')2140 procurement_obj = self.pool.get('procurement.order')
2141 context = context or {}2141 context = context or {}
2142 procurements = []
2143 cancel_ids = []
2142 for move in self.browse(cr, uid, ids, context=context):2144 for move in self.browse(cr, uid, ids, context=context):
2143 if move.state == 'done':2145 if move.state == 'done':
2144 raise osv.except_osv(_('Operation Forbidden!'),2146 continue #Could have been propagated from a forced move
2145 _('You cannot cancel a stock move that has been set to \'Done\'.'))2147 cancel_ids.append(move.id)
2146 if move.reserved_quant_ids:2148 if move.reserved_quant_ids:
2147 self.pool.get("stock.quant").quants_unreserve(cr, uid, move, context=context)2149 self.pool.get("stock.quant").quants_unreserve(cr, uid, move, context=context)
2148 if context.get('cancel_procurement'):2150 if context.get('cancel_procurement'):
2149 if move.propagate:2151 if move.propagate:
2150 procurement_ids = procurement_obj.search(cr, uid, [('move_dest_id', '=', move.id)], context=context)2152 procurement_ids = procurement_obj.search(cr, uid, [('move_dest_id', '=', move.id)], context=context)
2151 procurement_obj.cancel(cr, uid, procurement_ids, context=context)2153 procurement_obj.cancel(cr, uid, procurement_ids, context=context)
2152 elif move.move_dest_id:2154 elif move.move_dest_id and move.propagate:
2153 #cancel chained moves2155 #cancel chained moves
2154 if move.propagate:2156 self.action_cancel(cr, uid, [move.move_dest_id.id], context=context)
2155 self.action_cancel(cr, uid, [move.move_dest_id.id], context=context)2157 # If we have a long chain of moves to be cancelled, it is easier for the user to handle
2156 # If we have a long chain of moves to be cancelled, it is easier for the user to handle2158 # only the last procurement which will go into exception, instead of all procurements
2157 # only the last procurement which will go into exception, instead of all procurements2159 # along the chain going into exception. We need to check if there are no split moves not cancelled however
2158 # along the chain going into exception. We need to check if there are no split moves not cancelled however2160 if move.procurement_id:
2159 if move.procurement_id:2161 proc = move.procurement_id
2160 proc = move.procurement_id2162 if all([x.state == 'cancel' for x in proc.move_ids if x.id != move.id]):
2161 if all([x.state == 'cancel' for x in proc.move_ids if x.id != move.id]):2163 procurement_obj.write(cr, uid, [proc.id], {'state': 'cancel'})
2162 procurement_obj.write(cr, uid, [proc.id], {'state': 'cancel'})2164 elif move.procurement_id:
21632165 procurements.append(move.procurement_id.id)
2164 elif move.move_dest_id.state == 'waiting':2166 if cancel_ids:
2165 self.write(cr, uid, [move.move_dest_id.id], {'state': 'confirmed'}, context=context)2167 res = self.write(cr, uid, cancel_ids, {'state': 'cancel', 'move_dest_id': False}, context=context)
2166 return self.write(cr, uid, ids, {'state': 'cancel', 'move_dest_id': False}, context=context)2168 else:
2169 res = False
2170 if procurements:
2171 procurement_obj.check(cr, uid, procurements, context=context)
2172 return res
21672173
2168 def _check_package_from_moves(self, cr, uid, ids, context=None):2174 def _check_package_from_moves(self, cr, uid, ids, context=None):
2169 pack_obj = self.pool.get("stock.quant.package")2175 pack_obj = self.pool.get("stock.quant.package")
@@ -2354,7 +2360,7 @@
2354 returns the ID of the backorder move created2360 returns the ID of the backorder move created
2355 """2361 """
2356 if move.state in ('done', 'cancel'):2362 if move.state in ('done', 'cancel'):
2357 raise osv.except_osv(_('Error'), _('You cannot split a move done'))2363 return False #Could have been propagated to a forced move
2358 if move.state == 'draft':2364 if move.state == 'draft':
2359 #we restrict the split of a draft move because if not confirmed yet, it may be replaced by several other moves in2365 #we restrict the split of a draft move because if not confirmed yet, it may be replaced by several other moves in
2360 #case of phantom bom (with mrp module). And we don't want to deal with this complexity by copying the product that will explode.2366 #case of phantom bom (with mrp module). And we don't want to deal with this complexity by copying the product that will explode.