Merge lp:~openerp-community-reviewer/sale-wkfl/move_sale_exception_module_from_e-commerce-addons-jge into lp:~sale-core-editors/sale-wkfl/7.0

Proposed by Joël Grand-Guillaume @ camptocamp
Status: Merged
Merged at revision: 21
Proposed branch: lp:~openerp-community-reviewer/sale-wkfl/move_sale_exception_module_from_e-commerce-addons-jge
Merge into: lp:~sale-core-editors/sale-wkfl/7.0
Diff against target: 994 lines (+925/-0)
13 files modified
sale_exceptions/__init__.py (+26/-0)
sale_exceptions/__openerp__.py (+46/-0)
sale_exceptions/i18n/fr.po (+177/-0)
sale_exceptions/i18n/sale_exceptions.pot (+177/-0)
sale_exceptions/sale.py (+236/-0)
sale_exceptions/sale_exceptions_data.xml (+19/-0)
sale_exceptions/sale_view.xml (+102/-0)
sale_exceptions/sale_workflow.xml (+9/-0)
sale_exceptions/security/ir.model.access.csv (+3/-0)
sale_exceptions/settings/sale.exception.csv (+5/-0)
sale_exceptions/wizard/__init__.py (+24/-0)
sale_exceptions/wizard/sale_exception_confirm.py (+62/-0)
sale_exceptions/wizard/sale_exception_confirm_view.xml (+39/-0)
To merge this branch: bzr merge lp:~openerp-community-reviewer/sale-wkfl/move_sale_exception_module_from_e-commerce-addons-jge
Reviewer Review Type Date Requested Status
Nicolas Bessi - Camptocamp (community) no test, code review Approve
Guewen Baconnier @ Camptocamp Approve
Review via email: mp+193567@code.launchpad.net

Commit message

[MOVE] Move here the sale_exceptions module from the lp:e-commerce-addons

Description of the change

Hi,

I suggest to move the sale_exceptions module from e-commerce-addons to here because it really concerns the sales workflow in a more generic way than just the e-commerce context.

I put the branch under the reviewer team so feel free to fix stuff if you'd like.

WARNING: Merge this proposal when this one is done : https://code.launchpad.net/~openerp-community-reviewer/e-commerce-addons/move_sale_exception_to_sale_wkfl_branches-jge/+merge/193570

Regards,

Joël

To post a comment you must log in.
Revision history for this message
Pedro Manuel Baeza (pedro.baeza) wrote :

As your reasoning on community list, I approve it blindly.

Regards.

Revision history for this message
Sébastien BEAU - http://www.akretion.com (sebastien.beau) wrote :

Hi, I am working on the split of sale_exception in two module "sale_exception" and "exception_rule". The aim is to have the exception on different object like purchase_order, stock_picking, invoice ... (already implemented on purchase_order and soon on picking).
If we put the module sale_exception here where should I put the module "exception_rule" and "purchase_exception" (and later stock_picking_exception)?

I see two solution possible
- Putting exception_rule in "server-env-tools" and than all implementation in sale-wkfl, purchase-wkfl...
- Creating a project "workflow-exception" and put all exception module here

What do you prefers?

Revision history for this message
Pedro Manuel Baeza (pedro.baeza) wrote :

Hi, Sébastien, is there too much logic on exception_rule so that it is mandatory to split it in one module?, or it can be easily repeated in consequent other modules "purchase_exception", "stock_exception", etc?

Regards.

Revision history for this message
Joël Grand-Guillaume @ camptocamp (jgrandguillaume-c2c) wrote :

Hi,

+1 for Pedro. Having such a general module doesn't seems mandatory. The exception can IMO define in every module.

Moreover, the sale_exception module was already there like this. So I just move it. We can't assume doing both in one. Your suggestion may break back-compatibility so... My suggestion: move on to have it in the right project/branches, then when you'll be ready, make your MP so we'll have the opportunity to discuss that with something concrete !

What do you think ?

++

Joël

Revision history for this message
Sébastien BEAU - http://www.akretion.com (sebastien.beau) wrote :

After my split I have around 140 ligne of python in exception_rule and around 40 in sale_exception so I really think it's worth to split it because we will avoid to duplicate the 140 ligne in every module for nothing, it's not a lot but it's always better to no duplicate the code (on improvement improve all module). Moreover we use the same pop-up wizard in sale_exception and purchase_exception so it the same interface for the end user.

Here is my work : http://bazaar.launchpad.net/~sebastien.beau/e-commerce-addons/e-commerce-addons-split-sale-exception/view/head:/sale_exceptions/sale.py
(not ready to be merge yet, I have to be sure that I do not miss any improvement done by guewen here https://code.launchpad.net/~extra-addons-commiter/e-commerce-addons/7.0)

Revision history for this message
Sébastien BEAU - http://www.akretion.com (sebastien.beau) wrote :

Sorry I made a mistake around 180 line in the generic module

Revision history for this message
Joël Grand-Guillaume @ camptocamp (jgrandguillaume-c2c) wrote :

Hi Sébastien,

Your work looks great. Can you please make another MP once this one is done with your work ? This we move forward on this one.

@Pedro : Would you add your review here please ?

Regards,

Revision history for this message
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) wrote :

I approve for the move

review: Approve
46. By Nicolas Bessi - Camptocamp

[PEP8] and community conventions

47. By Nicolas Bessi - Camptocamp

[PEP8]

48. By Nicolas Bessi - Camptocamp

[PEP8] + removing class instantiation

Revision history for this message
Nicolas Bessi - Camptocamp (nbessi-c2c-deactivatedaccount) wrote :

LGTM

I also committed PEP8 and community convention fixes.

Moving the module is OK.

review: Approve (no test, code review)
49. By Nicolas Bessi - Camptocamp

[FIX] licence

50. By Nicolas Bessi - Camptocamp

[MRG] migration of sale_exceptions from original branch

Revision history for this message
Nicolas Bessi - Camptocamp (nbessi-c2c-deactivatedaccount) wrote :

Hello,

I have merge back migration/fixes recently commited in lp:e-commerce-addons

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory 'sale_exceptions'
2=== added file 'sale_exceptions/__init__.py'
3--- sale_exceptions/__init__.py 1970-01-01 00:00:00 +0000
4+++ sale_exceptions/__init__.py 2013-11-15 12:55:43 +0000
5@@ -0,0 +1,26 @@
6+# -*- coding: utf-8 -*-
7+##############################################################################
8+#
9+# OpenERP, Open Source Management Solution
10+# Copyright (C) 2011 Akretion LTDA.
11+# authors: Raphaël Valyi, Renato Lima
12+# Copyright (C) 2010-2012 Akretion Sébastien BEAU <sebastien.beau@akretion.com>
13+# Copyright (C) 2012 Camptocamp SA (Guewen Baconnier)
14+#
15+# This program is free software: you can redistribute it and/or modify
16+# it under the terms of the GNU Affero General Public License as
17+# published by the Free Software Foundation, either version 3 of the
18+# License, or (at your option) any later version.
19+#
20+# This program is distributed in the hope that it will be useful,
21+# but WITHOUT ANY WARRANTY; without even the implied warranty of
22+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23+# GNU Affero General Public License for more details.
24+#
25+# You should have received a copy of the GNU Affero General Public License
26+# along with this program. If not, see <http://www.gnu.org/licenses/>.
27+#
28+##############################################################################
29+
30+from . import sale
31+from . import wizard
32
33=== added file 'sale_exceptions/__openerp__.py'
34--- sale_exceptions/__openerp__.py 1970-01-01 00:00:00 +0000
35+++ sale_exceptions/__openerp__.py 2013-11-15 12:55:43 +0000
36@@ -0,0 +1,46 @@
37+# -*- coding: utf-8 -*-
38+##############################################################################
39+#
40+# OpenERP, Open Source Management Solution
41+# Copyright (C) 2011 Akretion LTDA.
42+# authors: Raphaël Valyi, Renato Lima
43+# Copyright (C) 2010-2012 Akretion Sébastien BEAU <sebastien.beau@akretion.com>
44+# Copyright (C) 2012 Camptocamp SA (Guewen Baconnier)
45+#
46+# This program is free software: you can redistribute it and/or modify
47+# it under the terms of the GNU Affero General Public License as
48+# published by the Free Software Foundation, either version 3 of the
49+# License, or (at your option) any later version.
50+#
51+# This program is distributed in the hope that it will be useful,
52+# but WITHOUT ANY WARRANTY; without even the implied warranty of
53+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
54+# GNU Affero General Public License for more details.
55+#
56+# You should have received a copy of the GNU Affero General Public License
57+# along with this program. If not, see <http://www.gnu.org/licenses/>.
58+#
59+##############################################################################
60+{'name': 'Sale Exceptions',
61+ 'version': '0.1',
62+ 'category': 'Generic Modules/Sale',
63+ 'description': """
64+This module allows you attach several customizable exceptions to your sale order
65+ in a way that you can filter orders by exceptions type and fix them.
66+
67+This is especially useful in an order importation scenario such as with
68+the base_sale_multi_channels module, because it's likely a few orders have errors
69+when you import them (like product not found in OpenERP, wrong line format etc...)
70+""",
71+ 'author': 'Akretion',
72+ 'website': 'http://www.akretion.com',
73+ 'depends': ['sale'],
74+ 'init_xml': ['settings/sale.exception.csv'],
75+ 'update_xml': ['sale_workflow.xml',
76+ 'sale_view.xml',
77+ 'sale_exceptions_data.xml',
78+ 'wizard/sale_exception_confirm_view.xml',
79+ 'security/ir.model.access.csv'],
80+ 'demo_xml': [],
81+ 'installable': True,
82+ }
83
84=== added directory 'sale_exceptions/i18n'
85=== added file 'sale_exceptions/i18n/fr.po'
86--- sale_exceptions/i18n/fr.po 1970-01-01 00:00:00 +0000
87+++ sale_exceptions/i18n/fr.po 2013-11-15 12:55:43 +0000
88@@ -0,0 +1,177 @@
89+# Translation of OpenERP Server.
90+# This file contains the translation of the following modules:
91+# * sale_exceptions
92+#
93+msgid ""
94+msgstr ""
95+"Project-Id-Version: OpenERP Server 7.0\n"
96+"Report-Msgid-Bugs-To: \n"
97+"POT-Creation-Date: 2013-05-02 06:57+0000\n"
98+"PO-Revision-Date: 2013-05-02 06:57+0000\n"
99+"Last-Translator: <>\n"
100+"Language-Team: \n"
101+"MIME-Version: 1.0\n"
102+"Content-Type: text/plain; charset=UTF-8\n"
103+"Content-Transfer-Encoding: \n"
104+"Plural-Forms: \n"
105+
106+#. module: sale_exceptions
107+#: model:ir.model,name:sale_exceptions.model_sale_exception_confirm
108+msgid "sale.exception.confirm"
109+msgstr ""
110+
111+#. module: sale_exceptions
112+#: selection:sale.exception,model:0
113+msgid "Sale Order Line"
114+msgstr "Ligne de commande"
115+
116+#. module: sale_exceptions
117+#: field:sale.exception,model:0
118+msgid "Apply on"
119+msgstr "Appliquer sur"
120+
121+#. module: sale_exceptions
122+#: model:sale.exception,name:sale_exceptions.excep_no_stock
123+msgid "Not Enough Virtual Stock"
124+msgstr "Pas assez de quantité de stock prévue"
125+
126+#. module: sale_exceptions
127+#: field:sale.exception,description:0
128+msgid "Description"
129+msgstr "Description"
130+
131+#. module: sale_exceptions
132+#: help:sale.exception,sequence:0
133+msgid "Gives the sequence order when applying the test"
134+msgstr "Définit l'ordre d'application des contrôles"
135+
136+#. module: sale_exceptions
137+#: view:sale.exception.confirm:0
138+msgid "Sale Exceptions On Sale Order"
139+msgstr "Restrictions sur la commande"
140+
141+#. module: sale_exceptions
142+#: field:sale.exception.confirm,exception_ids:0
143+msgid "Exceptions to resolve"
144+msgstr "Restrictions à résoudre"
145+
146+#. module: sale_exceptions
147+#: view:sale.exception.confirm:0
148+msgid "_Ok"
149+msgstr "_Ok"
150+
151+#. module: sale_exceptions
152+#: view:sale.exception:0
153+#: view:sale.exception.confirm:0
154+msgid "Sale Exception"
155+msgstr "Restriction de vente"
156+
157+#. module: sale_exceptions
158+#: view:sale.order:0
159+msgid "TO FIX"
160+msgstr "A CORRIGER"
161+
162+#. module: sale_exceptions
163+#: help:sale.exception,code:0
164+msgid "Python code executed to check if the exception apply or not. The code must apply block = True to apply the exception."
165+msgstr "Code Python exécuté pour déterminer si la restriction s'applique. The bloc de code doit retourner block = True pour appliquer la restriction."
166+
167+#. module: sale_exceptions
168+#: view:sale.order:0
169+msgid "Exception"
170+msgstr "Restriction"
171+
172+#. module: sale_exceptions
173+#: view:sale.order:0
174+msgid "Error:"
175+msgstr "Erreur :"
176+
177+#. module: sale_exceptions
178+#: selection:sale.exception,model:0
179+msgid "Sale Order"
180+msgstr "Bon de commande"
181+
182+#. module: sale_exceptions
183+#: field:sale.exception.confirm,sale_id:0
184+msgid "Sale"
185+msgstr "Commande"
186+
187+#. module: sale_exceptions
188+#: field:sale.exception,active:0
189+msgid "Active"
190+msgstr "Actif"
191+
192+#. module: sale_exceptions
193+#: field:sale.exception,name:0
194+msgid "Exception Name"
195+msgstr "Nom de la restriction"
196+
197+#. module: sale_exceptions
198+#: field:sale.order,exceptions_ids:0
199+msgid "Exceptions"
200+msgstr "Restrictions"
201+
202+#. module: sale_exceptions
203+#: model:ir.actions.act_window,name:sale_exceptions.action_sale_exception_confirm
204+#: model:ir.model,name:sale_exceptions.model_sale_exception
205+#: view:sale.exception.confirm:0
206+msgid "Sale Exceptions"
207+msgstr "Restrictions de vente"
208+
209+#. module: sale_exceptions
210+#: model:ir.actions.act_window,name:sale_exceptions.action_sale_test_tree
211+#: model:ir.ui.menu,name:sale_exceptions.menu_sale_test
212+msgid "Exception Rules"
213+msgstr "Règles de restriction"
214+
215+#. module: sale_exceptions
216+#: model:ir.model,name:sale_exceptions.model_sale_order
217+msgid "Sales Order"
218+msgstr "Bon de commande"
219+
220+#. module: sale_exceptions
221+#: field:sale.exception,sequence:0
222+msgid "Sequence"
223+msgstr "Séquence"
224+
225+#. module: sale_exceptions
226+#: field:sale.exception,code:0
227+msgid "Python Code"
228+msgstr "Code Python"
229+
230+#. module: sale_exceptions
231+#: view:sale.order:0
232+msgid "Sales"
233+msgstr "Bons de commande"
234+
235+#. module: sale_exceptions
236+#: model:sale.exception,name:sale_exceptions.excep_no_zip
237+msgid "No ZIP code on destination"
238+msgstr "Code postal manquant sur la destination"
239+
240+#. module: sale_exceptions
241+#: view:sale.exception:0
242+msgid "Sale Exception Setup"
243+msgstr "Configuration des restrictions de vente"
244+
245+#. module: sale_exceptions
246+#: view:sale.exception:0
247+msgid "Affected Sales Orders"
248+msgstr "Bons de commande affectés"
249+
250+#. module: sale_exceptions
251+#: field:sale.exception,sale_order_ids:0
252+msgid "Sale Orders"
253+msgstr "Bons de commande"
254+
255+#. module: sale_exceptions
256+#: field:sale.exception.confirm,ignore:0
257+#: field:sale.order,ignore_exceptions:0
258+msgid "Ignore Exceptions"
259+msgstr "Ignorer la restriction"
260+
261+#. module: sale_exceptions
262+#: field:sale.order,main_exception_id:0
263+msgid "Main Exception"
264+msgstr "Restriction principale"
265+
266
267=== added file 'sale_exceptions/i18n/sale_exceptions.pot'
268--- sale_exceptions/i18n/sale_exceptions.pot 1970-01-01 00:00:00 +0000
269+++ sale_exceptions/i18n/sale_exceptions.pot 2013-11-15 12:55:43 +0000
270@@ -0,0 +1,177 @@
271+# Translation of OpenERP Server.
272+# This file contains the translation of the following modules:
273+# * sale_exceptions
274+#
275+msgid ""
276+msgstr ""
277+"Project-Id-Version: OpenERP Server 7.0\n"
278+"Report-Msgid-Bugs-To: \n"
279+"POT-Creation-Date: 2013-05-02 06:57+0000\n"
280+"PO-Revision-Date: 2013-05-02 06:57+0000\n"
281+"Last-Translator: <>\n"
282+"Language-Team: \n"
283+"MIME-Version: 1.0\n"
284+"Content-Type: text/plain; charset=UTF-8\n"
285+"Content-Transfer-Encoding: \n"
286+"Plural-Forms: \n"
287+
288+#. module: sale_exceptions
289+#: model:ir.model,name:sale_exceptions.model_sale_exception_confirm
290+msgid "sale.exception.confirm"
291+msgstr ""
292+
293+#. module: sale_exceptions
294+#: selection:sale.exception,model:0
295+msgid "Sale Order Line"
296+msgstr ""
297+
298+#. module: sale_exceptions
299+#: field:sale.exception,model:0
300+msgid "Apply on"
301+msgstr ""
302+
303+#. module: sale_exceptions
304+#: model:sale.exception,name:sale_exceptions.excep_no_stock
305+msgid "Not Enough Virtual Stock"
306+msgstr ""
307+
308+#. module: sale_exceptions
309+#: field:sale.exception,description:0
310+msgid "Description"
311+msgstr ""
312+
313+#. module: sale_exceptions
314+#: help:sale.exception,sequence:0
315+msgid "Gives the sequence order when applying the test"
316+msgstr ""
317+
318+#. module: sale_exceptions
319+#: view:sale.exception.confirm:0
320+msgid "Sale Exceptions On Sale Order"
321+msgstr ""
322+
323+#. module: sale_exceptions
324+#: field:sale.exception.confirm,exception_ids:0
325+msgid "Exceptions to resolve"
326+msgstr ""
327+
328+#. module: sale_exceptions
329+#: view:sale.exception.confirm:0
330+msgid "_Ok"
331+msgstr ""
332+
333+#. module: sale_exceptions
334+#: view:sale.exception:0
335+#: view:sale.exception.confirm:0
336+msgid "Sale Exception"
337+msgstr ""
338+
339+#. module: sale_exceptions
340+#: view:sale.order:0
341+msgid "TO FIX"
342+msgstr ""
343+
344+#. module: sale_exceptions
345+#: help:sale.exception,code:0
346+msgid "Python code executed to check if the exception apply or not. The code must apply block = True to apply the exception."
347+msgstr ""
348+
349+#. module: sale_exceptions
350+#: view:sale.order:0
351+msgid "Exception"
352+msgstr ""
353+
354+#. module: sale_exceptions
355+#: view:sale.order:0
356+msgid "Error:"
357+msgstr ""
358+
359+#. module: sale_exceptions
360+#: selection:sale.exception,model:0
361+msgid "Sale Order"
362+msgstr ""
363+
364+#. module: sale_exceptions
365+#: field:sale.exception.confirm,sale_id:0
366+msgid "Sale"
367+msgstr ""
368+
369+#. module: sale_exceptions
370+#: field:sale.exception,active:0
371+msgid "Active"
372+msgstr ""
373+
374+#. module: sale_exceptions
375+#: field:sale.exception,name:0
376+msgid "Exception Name"
377+msgstr ""
378+
379+#. module: sale_exceptions
380+#: field:sale.order,exceptions_ids:0
381+msgid "Exceptions"
382+msgstr ""
383+
384+#. module: sale_exceptions
385+#: model:ir.actions.act_window,name:sale_exceptions.action_sale_exception_confirm
386+#: model:ir.model,name:sale_exceptions.model_sale_exception
387+#: view:sale.exception.confirm:0
388+msgid "Sale Exceptions"
389+msgstr ""
390+
391+#. module: sale_exceptions
392+#: model:ir.actions.act_window,name:sale_exceptions.action_sale_test_tree
393+#: model:ir.ui.menu,name:sale_exceptions.menu_sale_test
394+msgid "Exception Rules"
395+msgstr ""
396+
397+#. module: sale_exceptions
398+#: model:ir.model,name:sale_exceptions.model_sale_order
399+msgid "Sales Order"
400+msgstr ""
401+
402+#. module: sale_exceptions
403+#: field:sale.exception,sequence:0
404+msgid "Sequence"
405+msgstr ""
406+
407+#. module: sale_exceptions
408+#: field:sale.exception,code:0
409+msgid "Python Code"
410+msgstr ""
411+
412+#. module: sale_exceptions
413+#: view:sale.order:0
414+msgid "Sales"
415+msgstr ""
416+
417+#. module: sale_exceptions
418+#: model:sale.exception,name:sale_exceptions.excep_no_zip
419+msgid "No ZIP code on destination"
420+msgstr ""
421+
422+#. module: sale_exceptions
423+#: view:sale.exception:0
424+msgid "Sale Exception Setup"
425+msgstr ""
426+
427+#. module: sale_exceptions
428+#: view:sale.exception:0
429+msgid "Affected Sales Orders"
430+msgstr ""
431+
432+#. module: sale_exceptions
433+#: field:sale.exception,sale_order_ids:0
434+msgid "Sale Orders"
435+msgstr ""
436+
437+#. module: sale_exceptions
438+#: field:sale.exception.confirm,ignore:0
439+#: field:sale.order,ignore_exceptions:0
440+msgid "Ignore Exceptions"
441+msgstr ""
442+
443+#. module: sale_exceptions
444+#: field:sale.order,main_exception_id:0
445+msgid "Main Exception"
446+msgstr ""
447+
448
449=== added file 'sale_exceptions/sale.py'
450--- sale_exceptions/sale.py 1970-01-01 00:00:00 +0000
451+++ sale_exceptions/sale.py 2013-11-15 12:55:43 +0000
452@@ -0,0 +1,236 @@
453+# -*- coding: utf-8 -*-
454+##############################################################################
455+#
456+# OpenERP, Open Source Management Solution
457+# Copyright (C) 2011 Akretion LTDA.
458+# Copyright (C) 2010-2012 Akretion Sébastien BEAU <sebastien.beau@akretion.com>
459+# Copyright (C) 2012 Camptocamp SA (Guewen Baconnier)
460+#
461+# This program is free software: you can redistribute it and/or modify
462+# it under the terms of the GNU Affero General Public License as
463+# published by the Free Software Foundation, either version 3 of the
464+# License, or (at your option) any later version.
465+#
466+# This program is distributed in the hope that it will be useful,
467+# but WITHOUT ANY WARRANTY; without even the implied warranty of
468+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
469+# GNU Affero General Public License for more details.
470+#
471+# You should have received a copy of the GNU Affero General Public License
472+# along with this program. If not, see <http://www.gnu.org/licenses/>.
473+#
474+##############################################################################
475+
476+import time
477+
478+from openerp.osv import orm, fields
479+from openerp.tools.safe_eval import safe_eval
480+from openerp.tools.translate import _
481+
482+
483+class sale_exception(orm.Model):
484+ _name = "sale.exception"
485+ _description = "Sale Exceptions"
486+ _order = "active desc, sequence asc"
487+ _columns = {
488+ 'name': fields.char('Exception Name', required=True, translate=True),
489+ 'description': fields.text('Description', translate=True),
490+ 'sequence': fields.integer(
491+ 'Sequence',
492+ help="Gives the sequence order when applying the test"),
493+ 'model': fields.selection([('sale.order', 'Sale Order'),
494+ ('sale.order.line', 'Sale Order Line')],
495+ string='Apply on', required=True),
496+ 'active': fields.boolean('Active'),
497+ 'code': fields.text(
498+ 'Python Code',
499+ help="Python code executed to check if the exception apply or not. "
500+ "The code must apply block = True to apply the exception."),
501+ 'sale_order_ids': fields.many2many(
502+ 'sale.order',
503+ 'sale_order_exception_rel', 'exception_id', 'sale_order_id',
504+ string='Sale Orders',
505+ readonly=True),
506+ }
507+
508+ _defaults = {
509+ 'code': """# Python code. Use failed = True to block the sale order.
510+# You can use the following variables :
511+# - self: ORM model of the record which is checked
512+# - order or line: browse_record of the sale order or sale order line
513+# - object: same as order or line, browse_record of the sale order or sale order line
514+# - pool: ORM model pool (i.e. self.pool)
515+# - time: Python time module
516+# - cr: database cursor
517+# - uid: current user id
518+# - context: current context
519+"""
520+ }
521+
522+
523+class sale_order(orm.Model):
524+ _inherit = "sale.order"
525+
526+ _order = 'main_exception_id asc, date_order desc, name desc'
527+
528+ def _get_main_error(self, cr, uid, ids, name, args, context=None):
529+ res = {}
530+ for sale_order in self.browse(cr, uid, ids, context=context):
531+ if sale_order.state == 'draft' and sale_order.exceptions_ids:
532+ res[sale_order.id] = sale_order.exceptions_ids[0].id
533+ else:
534+ res[sale_order.id] = False
535+ return res
536+
537+ _columns = {
538+ 'main_exception_id': fields.function(
539+ _get_main_error,
540+ type='many2one',
541+ relation="sale.exception",
542+ string='Main Exception',
543+ store={
544+ 'sale.order': (lambda self, cr, uid, ids, c=None: ids,
545+ ['exceptions_ids', 'state'], 10),
546+ }),
547+ 'exceptions_ids': fields.many2many(
548+ 'sale.exception',
549+ 'sale_order_exception_rel', 'sale_order_id', 'exception_id',
550+ string='Exceptions'),
551+ 'ignore_exceptions': fields.boolean('Ignore Exceptions'),
552+ }
553+
554+ def test_all_draft_orders(self, cr, uid, context=None):
555+ ids = self.search(cr, uid, [('state', '=', 'draft')], context=context)
556+ self.test_exceptions(cr, uid, ids, context=context)
557+ return True
558+
559+ def _popup_exceptions(self, cr, uid, order_id, context=None):
560+ if context is None:
561+ context = {}
562+ model_data_obj = self.pool.get('ir.model.data')
563+ list_obj = self.pool.get('sale.exception.confirm')
564+ ctx = context.copy()
565+ ctx.update({'active_id': order_id,
566+ 'active_ids': [order_id]})
567+ list_id = list_obj.create(cr, uid, {}, context=ctx)
568+ view_id = model_data_obj.get_object_reference(
569+ cr, uid, 'sale_exceptions', 'view_sale_exception_confirm')[1]
570+ action = {
571+ 'name': _("Blocked in draft due to exceptions"),
572+ 'type': 'ir.actions.act_window',
573+ 'view_type': 'form',
574+ 'view_mode': 'form',
575+ 'res_model': 'sale.exception.confirm',
576+ 'view_id': [view_id],
577+ 'target': 'new',
578+ 'nodestroy': True,
579+ 'res_id': list_id,
580+ }
581+ return action
582+
583+ def action_button_confirm(self, cr, uid, ids, context=None):
584+ exception_ids = self.detect_exceptions(cr, uid, ids, context=context)
585+ if exception_ids:
586+ return self._popup_exceptions(cr, uid, ids[0], context=context)
587+ else:
588+ return super(sale_order, self).action_button_confirm(cr, uid, ids,
589+ context=context)
590+
591+ def test_exceptions(self, cr, uid, ids, context=None):
592+ """
593+ Condition method for the workflow from draft to confirm
594+ """
595+ exception_ids = self.detect_exceptions(cr, uid, ids, context=context)
596+ if exception_ids:
597+ return False
598+ return True
599+
600+ def detect_exceptions(self, cr, uid, ids, context=None):
601+ exception_obj = self.pool.get('sale.exception')
602+ order_exception_ids = exception_obj.search(
603+ cr, uid,
604+ [('model', '=', 'sale.order')],
605+ context=context)
606+ line_exception_ids = exception_obj.search(
607+ cr, uid,
608+ [('model', '=', 'sale.order.line')],
609+ context=context)
610+
611+ order_exceptions = exception_obj.browse(cr, uid, order_exception_ids,
612+ context=context)
613+ line_exceptions = exception_obj.browse(cr, uid, line_exception_ids,
614+ context=context)
615+
616+ exception_ids = False
617+ for order in self.browse(cr, uid, ids, context=context):
618+ if order.ignore_exceptions:
619+ continue
620+ exception_ids = self._detect_exceptions(cr, uid,
621+ order,
622+ order_exceptions,
623+ line_exceptions,
624+ context=context)
625+
626+ self.write(cr, uid, [order.id],
627+ {'exceptions_ids': [(6, 0, exception_ids)]},
628+ context=context)
629+ return exception_ids
630+
631+ def _exception_rule_eval_context(self, cr, uid, obj_name, obj, context=None):
632+ if context is None:
633+ context = {}
634+
635+ user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
636+ return {obj_name: obj,
637+ 'self': self.pool.get(obj._name),
638+ 'object': obj,
639+ 'obj': obj,
640+ 'pool': self.pool,
641+ 'cr': cr,
642+ 'uid': uid,
643+ 'user': user,
644+ 'time': time,
645+ # copy context to prevent side-effects of eval
646+ 'context': context.copy()}
647+
648+ def _rule_eval(self, cr, uid, rule, obj_name, obj, context):
649+ expr = rule.code
650+ space = self._exception_rule_eval_context(cr, uid, obj_name, obj,
651+ context=context)
652+ try:
653+ safe_eval(expr,
654+ space,
655+ mode='exec',
656+ nocopy=True) # nocopy allows to return 'result'
657+ except Exception, e:
658+ raise orm.except_orm(
659+ _('Error'),
660+ _('Error when evaluating the sale exception '
661+ 'rule:\n %s \n(%s)') % (rule.name, e))
662+ return space.get('failed', False)
663+
664+ def _detect_exceptions(self, cr, uid, order, order_exceptions,
665+ line_exceptions, context=None):
666+ exception_ids = []
667+ for rule in order_exceptions:
668+ if self._rule_eval(cr, uid, rule, 'order', order, context):
669+ exception_ids.append(rule.id)
670+
671+ for order_line in order.order_line:
672+ for rule in line_exceptions:
673+ if rule.id in exception_ids:
674+ # we do not matter if the exception as already been
675+ # found for an order line of this order
676+ continue
677+ if self._rule_eval(cr, uid, rule, 'line', order_line, context):
678+ exception_ids.append(rule.id)
679+
680+ return exception_ids
681+
682+ def copy(self, cr, uid, id, default=None, context=None):
683+ if default is None:
684+ default = {}
685+ default.update({
686+ 'ignore_exceptions': False,
687+ })
688+ return super(sale_order, self).copy(cr, uid, id, default=default, context=context)
689
690=== added file 'sale_exceptions/sale_exceptions_data.xml'
691--- sale_exceptions/sale_exceptions_data.xml 1970-01-01 00:00:00 +0000
692+++ sale_exceptions/sale_exceptions_data.xml 2013-11-15 12:55:43 +0000
693@@ -0,0 +1,19 @@
694+<?xml version="1.0" encoding="utf-8"?>
695+<openerp>
696+ <data noupdate="1">
697+
698+ <record forcecreate="True" id="ir_cron_test_orders" model="ir.cron">
699+ <field name="name">Test Draft Orders</field>
700+ <field eval="False" name="active"/>
701+ <field name="user_id" ref="base.user_root"/>
702+ <field name="interval_number">20</field>
703+ <field name="interval_type">minutes</field>
704+ <field name="numbercall">-1</field>
705+ <field eval="False" name="doall"/>
706+ <field eval="'sale.order'" name="model"/>
707+ <field eval="'test_all_draft_orders'" name="function"/>
708+ <field eval="'()'" name="args"/>
709+ </record>
710+
711+ </data>
712+</openerp>
713
714=== added file 'sale_exceptions/sale_view.xml'
715--- sale_exceptions/sale_view.xml 1970-01-01 00:00:00 +0000
716+++ sale_exceptions/sale_view.xml 2013-11-15 12:55:43 +0000
717@@ -0,0 +1,102 @@
718+<?xml version="1.0" ?>
719+<openerp>
720+ <data>
721+
722+ <record id="view_sale_exception_tree" model="ir.ui.view">
723+ <field name="name">sale.exception.tree</field>
724+ <field name="model">sale.exception</field>
725+ <field name="arch" type="xml">
726+ <tree string="Sale Exception">
727+ <field name="active"/>
728+ <field name="name"/>
729+ <field name="description"/>
730+ <field name="model"/>
731+ <field name="sequence"/>
732+ </tree>
733+ </field>
734+ </record>
735+
736+ <record id="view_sale_exception_form" model="ir.ui.view">
737+ <field name="name">sale.exception.form</field>
738+ <field name="model">sale.exception</field>
739+ <field name="arch" type="xml">
740+ <form string="Sale Exception Setup">
741+ <group colspan="4" col="2">
742+ <field name="name"/>
743+ <field name="description"/>
744+ </group>
745+ <group col="4" colspan="4" groups="base.group_sale_manager">
746+ <field name="active"/>
747+ <field name="sequence"/>
748+ <group colspan="4" col="2" groups="base.group_system">
749+ <field name="model"/>
750+ <field name="code"/>
751+ </group>
752+ </group>
753+ <group colspan="4" col="2">
754+ <separator string="Affected Sales Orders"/>
755+ <newline/>
756+ <field name="sale_order_ids" nolabel="1" domain="[('state', '=', 'draft')]"/>
757+ </group>
758+ </form>
759+ </field>
760+ </record>
761+
762+ <record id="action_sale_test_tree" model="ir.actions.act_window">
763+ <field name="name">Exception Rules</field>
764+ <field name="res_model">sale.exception</field>
765+ <field name="view_type">form</field>
766+ <field name="view_mode">tree,form</field>
767+ <field name="view_id" ref="view_sale_exception_tree"/>
768+ <field name="context">{'active_test': False}</field>
769+ </record>
770+
771+ <menuitem action="action_sale_test_tree" id="menu_sale_test" parent="base.menu_sale_config_sales" />
772+
773+
774+ <record id="view_order_form" model="ir.ui.view">
775+ <field name="name">sale_exceptions.view_order_form</field>
776+ <field name="model">sale.order</field>
777+ <field name="inherit_id" ref="sale.view_order_form"/>
778+ <field name="arch" type="xml">
779+ <field name="name" position="after">
780+ <group>
781+ <field name="main_exception_id" options='{"no_open": True}'
782+ class="oe_inline" string="Error:"
783+ attrs="{'invisible':[('main_exception_id','=', False)]}"/>
784+ </group>
785+ </field>
786+ <xpath expr="//page[@string='Other Information']/group"
787+ position="inside">
788+ <group name="exception" colspan="2" col="2">
789+ <separator string="Exception" colspan="2"/>
790+ <field name="exceptions_ids" colspan="2" nolabel="1"/>
791+ </group>
792+ </xpath>
793+ </field>
794+ </record>
795+
796+ <record id="view_order_tree" model="ir.ui.view">
797+ <field name="name">sale_exceptions.view_order_tree</field>
798+ <field name="model">sale.order</field>
799+ <field name="inherit_id" ref="sale.view_order_tree"/>
800+ <field name="arch" type="xml">
801+ <field name="state" position="after">
802+ <field name="main_exception_id"/>
803+ </field>
804+ </field>
805+ </record>
806+
807+ <record id="view_sales_order_filter" model="ir.ui.view">
808+ <field name="name">sale_exceptions.view_sales_order_filter</field>
809+ <field name="model">sale.order</field>
810+ <field name="inherit_id" ref="sale.view_sales_order_filter" />
811+ <field name="arch" type="xml">
812+ <filter name="sales" position="after">
813+ <separator orientation="vertical"/>
814+ <filter icon="terp-emblem-important" name="tofix" string="Blocked in draft" domain="[('main_exception_id','!=',False)]"/>
815+ </filter>
816+ </field>
817+ </record>
818+ </data>
819+</openerp>
820
821=== added file 'sale_exceptions/sale_workflow.xml'
822--- sale_exceptions/sale_workflow.xml 1970-01-01 00:00:00 +0000
823+++ sale_exceptions/sale_workflow.xml 2013-11-15 12:55:43 +0000
824@@ -0,0 +1,9 @@
825+<?xml version="1.0" encoding="utf-8"?>
826+<openerp>
827+ <data>
828+ <record id="sale.trans_draft_router" model="workflow.transition">
829+ <field name="signal">order_confirm</field>
830+ <field name="condition">test_exceptions()</field>
831+ </record>
832+ </data>
833+</openerp>
834
835=== added directory 'sale_exceptions/security'
836=== added file 'sale_exceptions/security/ir.model.access.csv'
837--- sale_exceptions/security/ir.model.access.csv 1970-01-01 00:00:00 +0000
838+++ sale_exceptions/security/ir.model.access.csv 2013-11-15 12:55:43 +0000
839@@ -0,0 +1,3 @@
840+"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
841+"access_sale_exception","sale.exception","model_sale_exception","base.group_user",1,0,0,0
842+"access_sale_exception_manager","sale.exception","model_sale_exception","base.group_sale_manager",1,1,1,1
843
844=== added directory 'sale_exceptions/settings'
845=== added file 'sale_exceptions/settings/sale.exception.csv'
846--- sale_exceptions/settings/sale.exception.csv 1970-01-01 00:00:00 +0000
847+++ sale_exceptions/settings/sale.exception.csv 2013-11-15 12:55:43 +0000
848@@ -0,0 +1,5 @@
849+"id","name","description","sequence","model","code","active"
850+"excep_no_zip","No ZIP code on destination",,50,"sale.order","if not order.partner_shipping_id.zip:
851+ failed=True",False
852+"excep_no_stock","Not Enough Virtual Stock",,50,"sale.order.line","if line.product_id and line.product_id.type == 'product' and line.product_id.virtual_available < line.product_uom_qty:
853+ failed=True",False
854
855=== added directory 'sale_exceptions/wizard'
856=== added file 'sale_exceptions/wizard/__init__.py'
857--- sale_exceptions/wizard/__init__.py 1970-01-01 00:00:00 +0000
858+++ sale_exceptions/wizard/__init__.py 2013-11-15 12:55:43 +0000
859@@ -0,0 +1,24 @@
860+# -*- coding: utf-8 -*-
861+##############################################################################
862+#
863+# OpenERP, Open Source Management Solution
864+# Copyright (C) 2011 Akretion LTDA.
865+# authors: Raphaël Valyi, Renato Lima
866+# Copyright (C) 2010-2012 Akretion Sébastien BEAU <sebastien.beau@akretion.com>
867+# Copyright (C) 2012 Camptocamp SA (Guewen Baconnier)
868+#
869+# This program is free software: you can redistribute it and/or modify
870+# it under the terms of the GNU Affero General Public License as
871+# published by the Free Software Foundation, either version 3 of the
872+# License, or (at your option) any later version.
873+#
874+# This program is distributed in the hope that it will be useful,
875+# but WITHOUT ANY WARRANTY; without even the implied warranty of
876+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
877+# GNU Affero General Public License for more details.
878+#
879+# You should have received a copy of the GNU Affero General Public License
880+# along with this program. If not, see <http://www.gnu.org/licenses/>.
881+#
882+##############################################################################
883+from . import sale_exception_confirm
884
885=== added file 'sale_exceptions/wizard/sale_exception_confirm.py'
886--- sale_exceptions/wizard/sale_exception_confirm.py 1970-01-01 00:00:00 +0000
887+++ sale_exceptions/wizard/sale_exception_confirm.py 2013-11-15 12:55:43 +0000
888@@ -0,0 +1,62 @@
889+# -*- coding: utf-8 -*-
890+##############################################################################
891+#
892+# Copyright Camptocamp SA
893+# @author: Guewen Baconnier
894+#
895+# This program is free software: you can redistribute it and/or modify
896+# it under the terms of the GNU General Public License as published by
897+# the Free Software Foundation, either version 3 of the License, or
898+# (at your option) any later version.
899+#
900+# This program is distributed in the hope that it will be useful,
901+# but WITHOUT ANY WARRANTY; without even the implied warranty of
902+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
903+# GNU General Public License for more details.
904+#
905+# You should have received a copy of the GNU General Public License
906+# along with this program. If not, see <http://www.gnu.org/licenses/>.
907+#
908+##############################################################################
909+from openerp.osv import orm, fields
910+
911+
912+class SaleExceptionConfirm(orm.TransientModel):
913+
914+ _name = 'sale.exception.confirm'
915+
916+ _columns = {
917+ 'sale_id': fields.many2one('sale.order', 'Sale'),
918+ 'exception_ids': fields.many2many('sale.exception',
919+ string='Exceptions to resolve',
920+ readonly=True),
921+ 'ignore': fields.boolean('Ignore Exceptions'),
922+ }
923+
924+ def default_get(self, cr, uid, fields, context=None):
925+ if context is None:
926+ context = {}
927+ res = super(SaleExceptionConfirm, self).default_get(
928+ cr, uid, fields, context=context)
929+ order_obj = self.pool.get('sale.order')
930+ sale_id = context.get('active_ids')
931+ assert len(sale_id) == 1, "Only 1 ID accepted, got %r" % sale_id
932+ sale_id = sale_id[0]
933+ sale = order_obj.browse(cr, uid, sale_id, context=context)
934+ exception_ids = [e.id for e in sale.exceptions_ids]
935+ res.update({'exception_ids': [(6, 0, exception_ids)]})
936+ res.update({'sale_id': sale_id})
937+ return res
938+
939+ def action_confirm(self, cr, uid, ids, context=None):
940+ if hasattr(ids, '__iter__'):
941+ assert len(ids) == 1, "Only 1 ID accepted, got %r" % ids
942+ ids = ids[0]
943+ form = self.browse(cr, uid, ids, context=context)
944+ if form.ignore:
945+ self.pool.get('sale.order').write(
946+ cr, uid,
947+ form.sale_id.id,
948+ {'ignore_exceptions': True},
949+ context=context)
950+ return {'type': 'ir.actions.act_window_close'}
951
952=== added file 'sale_exceptions/wizard/sale_exception_confirm_view.xml'
953--- sale_exceptions/wizard/sale_exception_confirm_view.xml 1970-01-01 00:00:00 +0000
954+++ sale_exceptions/wizard/sale_exception_confirm_view.xml 2013-11-15 12:55:43 +0000
955@@ -0,0 +1,39 @@
956+<?xml version="1.0" encoding="utf-8"?>
957+<openerp>
958+ <data>
959+
960+ <record id="view_sale_exception_confirm" model="ir.ui.view">
961+ <field name="name">Sale Exceptions</field>
962+ <field name="model">sale.exception.confirm</field>
963+ <field name="arch" type="xml">
964+ <form string="Blocked in draft due to exceptions" version="7.0">
965+ <group>
966+ <field name="exception_ids" nolabel="1" colspan="4">
967+ <tree string="Sale Exceptions">
968+ <field name="name"/>
969+ <field name="description"/>
970+ </tree>
971+ </field>
972+ <newline/>
973+ <field name="ignore" groups='base.group_sale_manager'/>
974+ </group>
975+ <footer>
976+ <button name="action_confirm" string="_Close"
977+ colspan="1" type="object" icon="gtk-ok" />
978+ </footer>
979+ </form>
980+ </field>
981+ </record>
982+
983+ <record id="action_sale_exception_confirm" model="ir.actions.act_window">
984+ <field name="name">Blocked in draft due to exceptions</field>
985+ <field name="type">ir.actions.act_window</field>
986+ <field name="res_model">sale.exception.confirm</field>
987+ <field name="view_type">form</field>
988+ <field name="view_mode">form</field>
989+ <field name="view_id" ref="view_sale_exception_confirm"/>
990+ <field name="target">new</field>
991+ </record>
992+
993+ </data>
994+</openerp>

Subscribers

People subscribed via source and target branches