Merge lp:~akretion-team/stock-logistic-flows/70-product_serial-plus-plus into lp:stock-logistic-flows

Proposed by Alexis de Lattre
Status: Needs review
Proposed branch: lp:~akretion-team/stock-logistic-flows/70-product_serial-plus-plus
Merge into: lp:stock-logistic-flows
Diff against target: 1428 lines (+888/-189)
16 files modified
product_serial/__init__.py (+4/-5)
product_serial/i18n/product_serial.pot (+211/-43)
product_serial/product_demo.xml (+1/-0)
product_serial/stock.py (+11/-6)
product_serial/stock_view.xml (+3/-81)
product_serial/wizard/__init__.py (+1/-2)
product_serial/wizard/prodlot_wizard.py (+135/-44)
product_serial/wizard/prodlot_wizard_view.xml (+14/-8)
product_serial_purchase/__init__.py (+23/-0)
product_serial_purchase/__openerp__.py (+44/-0)
product_serial_purchase/i18n/product_serial_purchase.pot (+34/-0)
product_serial_purchase/purchase.py (+131/-0)
product_serial_sale_stock/__init__.py (+23/-0)
product_serial_sale_stock/__openerp__.py (+46/-0)
product_serial_sale_stock/i18n/product_serial_sale_stock.pot (+34/-0)
product_serial_sale_stock/sale_stock.py (+173/-0)
To merge this branch: bzr merge lp:~akretion-team/stock-logistic-flows/70-product_serial-plus-plus
Reviewer Review Type Date Requested Status
Alexandre Fayolle - camptocamp Needs Resubmitting
Joël Grand-Guillaume @ camptocamp code review, no tests Approve
Yannick Vaucher @ Camptocamp Needs Information
Lionel Sausin - Initiatives/Numérigraphe (community) Abstain
Raphaël Valyi - http://www.akretion.com Approve
Review via email: mp+195144@code.launchpad.net

Description of the change

This merge proposal is for the module product_serial.

The main new feature of this merge proposal is the ability to select or create prodlots from a text file (simple texte file, 1 line per prodlot)

Other changes :
- Keep the native "split in serial number" button, because it can be usefull for products that need manual split in non-predictable lot size.
- Code cleaning : reduce flake8 warnings in the wizard, remove old code

I hope you will like this merge proposal :-)

To post a comment you must log in.
39. By Alexis de Lattre

[MERGE] with lp:stock-logistic-flows/7.0 revno 39.

40. By Alexis de Lattre

Add the new field track_internal to demo data.

41. By Alexis de Lattre

Add 2 small modules product_serial_purchase and product_serial_sale_stock.

Revision history for this message
Raphaël Valyi - http://www.akretion.com (rvalyi) wrote :

LGTM, lot's f welcome code cleaning, thanks.

review: Approve
Revision history for this message
Lionel Sausin - Initiatives/Numérigraphe (ls-initiatives) wrote :

product_serial looks like a great module but it already has 4 functional features.
I humbly suggest it would be better to split it into several small specialized modules, and let user cherry-pick the ones they need.
I would make the code easier to understand and to extend.

review: Needs Fixing
Revision history for this message
Alexis de Lattre (alexis-via) wrote :

@Lionel

I agree with you ; my merge proposal is a first step in this direction, with the introduction of :
- product_serial_sale_stock
- product_serial_purchase

but I won't do the split you talk about in this merge proposal, because it's out of the scope of this MP. It would have to be done in a new MP dedicated to this task.

Revision history for this message
Lionel Sausin - Initiatives/Numérigraphe (ls-initiatives) :
review: Abstain
Revision history for this message
Lionel Sausin - Initiatives/Numérigraphe (ls-initiatives) wrote :

Sorry Alexis de Lattre, I didn't notice the modules you introduced.
You're right a dedicated MP for the splitting will be more effective.

42. By Alexis de Lattre

[MERGE] with main branch revno 40.

43. By Alexis de Lattre

Modularize the code of the wizard for the module product_serial_mrp

44. By Alexis de Lattre

Harmonize with the OpenERP strings : Production Lots -> Serial Numbers

Revision history for this message
Yannick Vaucher @ Camptocamp (yvaucher-c2c) wrote :

I'll approve it under the condition this will be splited.
You should already create an other MP which has this one as prerequisite.

Will you split that added feature of serial number in a module as well ?

Otherwise thanks for the code cleaning.

Cheers

review: Needs Information
Revision history for this message
Alexis de Lattre (alexis-via) wrote :

@Yannick:

As I said, this MP is a first step towards a split ; as you can see, it introduce 2 new modules : product_serial_sale_stock and product_serial_purchase. I am about to do an MP on lp:openerp-manufacturing for product_serial_mrp.

BUT I didn't say that i'll do all the work for the split by myself. You say : "You should already create an other MP" : you are also invited to do it, it's a community work, not just my own work ! I am not the original author of the module product_serial, I am just a modest contributor on this module.

Revision history for this message
Alexandre Fayolle - camptocamp (alexandre-fayolle-c2c) wrote :

* in select_or_create_prodlots_from_interval:

The following lines

282 + prodlot_seq = ['%%s%%0%dd%%s' % number_length % (
283 + prefix, current_number, suffix) for current_number in
284 + range(first_number, last_number+1)
285 + ]

should be rewritten using '%s%0*d%s' % (prefix, current_number, number_length, suffix) (see http://docs.python.org/2.7/library/stdtypes.html#string-formatting-operations)

I also suggest testing that the interval limits are > 0

* spell check: s/splitted/split/ everywhere

* product_serial_sale_stock/sale_stock.py : the code is not calling super()._create_pickings_and_procurements and will therefore break any addons which overloads this method. If you really need to reimplement the method without playing the super game, this needs at least to be proeminently documented in the module description. And I think monkey patching the official addon implementation would be less disruptive to other addons.

* there are no automated tests for the new feature.

review: Needs Fixing (code review, no test)
45. By Alexis de Lattre

As suggested by Alexandre Fayolle:
- splitted -> split (English irregular verb)
- simplify string formatting in the spread prodlot wizard
- in the speard prodlot wizard, check that the numbers are > 0
- add a warning in the description of the module product_serial_sale_stock that the module doesn't call super()

Update POT file

Revision history for this message
Alexis de Lattre (alexis-via) wrote :

@Alexandre

Thanks for your review.

You are right, the fact that I don't call super() on _create_pickings_and_procurements() in the module product_serial_sale_stock is a big problem and I am aware of that. But, unfortunately, I didn't find a solution that includes calling super(). If you think it's possible, please give me some ideas !

I am not an expert of monkey patching, but I'll see with my colleagues if it is possible.

And thanks for pointing out my mistakes on English irregular verbs :)

46. By Alexis de Lattre

Use monkey-patching in product_serial_sale_stock, as suggested by Alexandre Fayolle.
[FIX] Copy splitted qty to product_uos_qty, because this field is used for invoicing !

47. By Alexis de Lattre

PEP-8 stuff (I forgot to check it before my previous commit)

48. By Alexis de Lattre

Small change in import (idea of Sebastien Beau)

Revision history for this message
Alexis de Lattre (alexis-via) wrote :

@Alexandre:

Could you update your review status "Needs fixing" following my commits 45 to 48 ?

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

Hi Alexis,

Thanks for this contribs. I checked the corrected points suggested by Alexandre and that's fine now !

The only point I see is that we have no tests provided. Can I ask you to add at least one when making the next MP on splitting the module ?

This is a feature we need to be able to rely on, therefor having tests is important here IMO.

I don't want to block this MP any longer, but please think on adding tests the next time.

Best regards,

Joël

review: Approve (code review, no tests)
Revision history for this message
Alexandre Fayolle - camptocamp (alexandre-fayolle-c2c) wrote :

The source code management for this project has been moved to https://github.com/OCA/stock-logistics-workflow

Could you resubmit this MP on the new site?

review: Needs Resubmitting
Revision history for this message
Alexis de Lattre (alexis-via) wrote :

Well... product_serial will have to be largely re-written for v8, given all the big changes in stock management. If the modules in v7.0 stay on LP, we could keep this branch here.

Revision history for this message
Leonardo Pistone (lepistone) wrote :

Alexis, They don't! :)

On Thu, Jul 3, 2014 at 6:16 PM, Alexis de Lattre <email address hidden>
wrote:

> Well... product_serial will have to be largely re-written for v8, given all the big changes in stock management. If the modules in v7.0 stay on LP, we could keep this branch here.
> --
> https://code.launchpad.net/~akretion-team/stock-logistic-flows/70-product_serial-plus-plus/+merge/195144
> Your team Stock and Logistic Core Editors is subscribed to branch lp:stock-logistic-flows.
> --
> Mailing list: https://launchpad.net/~openerp-community-reviewer
> Post to : <email address hidden>
> Unsubscribe : https://launchpad.net/~openerp-community-reviewer
> More help : https://help.launchpad.net/ListHelp

49. By Alexis de Lattre

Add missing dependancy.

50. By Alexis de Lattre

FIX a bug that was causing wrong stock level when the qty was changed manually by the user in a PO generated by procurements.

51. By Alexis de Lattre

FIX my previous fix : don't fail when line.move_dest_id is False

52. By Alexis de Lattre

FIX avoid a crash when the module product_serial_sale_stock is not installed.

53. By Alexis de Lattre

Using split('\n') is not a good idea when we have windows files that use '\n\r' for new lines.

54. By Alexis de Lattre

FIX avoid infinite loop when product qty on sale order line is 0 !

55. By Alexis de Lattre

Add prepare method for prodlot generation

Unmerged revisions

55. By Alexis de Lattre

Add prepare method for prodlot generation

54. By Alexis de Lattre

FIX avoid infinite loop when product qty on sale order line is 0 !

53. By Alexis de Lattre

Using split('\n') is not a good idea when we have windows files that use '\n\r' for new lines.

52. By Alexis de Lattre

FIX avoid a crash when the module product_serial_sale_stock is not installed.

51. By Alexis de Lattre

FIX my previous fix : don't fail when line.move_dest_id is False

50. By Alexis de Lattre

FIX a bug that was causing wrong stock level when the qty was changed manually by the user in a PO generated by procurements.

49. By Alexis de Lattre

Add missing dependancy.

48. By Alexis de Lattre

Small change in import (idea of Sebastien Beau)

47. By Alexis de Lattre

PEP-8 stuff (I forgot to check it before my previous commit)

46. By Alexis de Lattre

Use monkey-patching in product_serial_sale_stock, as suggested by Alexandre Fayolle.
[FIX] Copy splitted qty to product_uos_qty, because this field is used for invoicing !

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'product_serial/__init__.py'
2--- product_serial/__init__.py 2013-08-01 12:40:27 +0000
3+++ product_serial/__init__.py 2015-12-04 10:45:32 +0000
4@@ -19,8 +19,7 @@
5 #
6 ##############################################################################
7
8-import product
9-import stock
10-import company
11-import wizard
12-
13+from . import product
14+from . import stock
15+from . import company
16+from . import wizard
17
18=== modified file 'product_serial/i18n/product_serial.pot'
19--- product_serial/i18n/product_serial.pot 2013-11-29 15:22:57 +0000
20+++ product_serial/i18n/product_serial.pot 2015-12-04 10:45:32 +0000
21@@ -1,13 +1,13 @@
22 # Translation of OpenERP Server.
23 # This file contains the translation of the following modules:
24-# * product_serial
25+# * product_serial
26 #
27 msgid ""
28 msgstr ""
29 "Project-Id-Version: OpenERP Server 7.0\n"
30 "Report-Msgid-Bugs-To: \n"
31-"POT-Creation-Date: 2013-10-09 09:30+0000\n"
32-"PO-Revision-Date: 2013-11-29 16:22+0100\n"
33+"POT-Creation-Date: 2014-03-19 13:36+0000\n"
34+"PO-Revision-Date: 2014-03-19 13:36+0000\n"
35 "Last-Translator: <>\n"
36 "Language-Team: \n"
37 "MIME-Version: 1.0\n"
38@@ -21,8 +21,27 @@
39 msgstr ""
40
41 #. module: product_serial
42-#: selection:product.product,lot_split_type:0
43-msgid "Single"
44+#: code:addons/product_serial/stock.py:146
45+#, python-format
46+msgid "Product '%s' has 'Lot split type' = 'Logistical Unit' but is missing packaging information."
47+msgstr ""
48+
49+#. module: product_serial
50+#: view:stock.picking:0
51+#: view:stock.picking.in:0
52+#: view:stock.picking.out:0
53+msgid "Spread Serial Numbers"
54+msgstr ""
55+
56+#. module: product_serial
57+#: field:product.product,track_internal:0
58+msgid "Track Lots internally"
59+msgstr ""
60+
61+#. module: product_serial
62+#: code:addons/product_serial/wizard/prodlot_wizard.py:122
63+#, python-format
64+msgid "The field 'First Number' should only contain digits."
65 msgstr ""
66
67 #. module: product_serial
68@@ -31,18 +50,30 @@
69 msgstr ""
70
71 #. module: product_serial
72-#: selection:product.product,lot_split_type:0
73-msgid "None"
74-msgstr ""
75-
76-#. module: product_serial
77-#: model:ir.model,name:product_serial.model_stock_picking_prodlot_selection
78-msgid "stock.picking.prodlot.selection"
79-msgstr ""
80-
81-#. module: product_serial
82-#: field:stock.production.lot,last_location_id:0
83-msgid "Last location"
84+#: code:addons/product_serial/wizard/prodlot_wizard.py:129
85+#, python-format
86+msgid "The field 'Last Number' should only contain digits."
87+msgstr ""
88+
89+#. module: product_serial
90+#: help:res.company,is_group_invoice_line:0
91+msgid "If active, OpenERP will group the identical invoice lines when generating an invoice from a picking. If inactive, each move line will generate one invoice line."
92+msgstr ""
93+
94+#. module: product_serial
95+#: view:stock.move:0
96+msgid "onchange_product_id(product_id,location_id,location_dest_id, False)"
97+msgstr ""
98+
99+#. module: product_serial
100+#: view:stock.picking.prodlot.selection:0
101+msgid "Run From Interval"
102+msgstr ""
103+
104+#. module: product_serial
105+#: code:addons/product_serial/wizard/prodlot_wizard.py:144
106+#, python-format
107+msgid "First and Last Numbers must have the same length."
108 msgstr ""
109
110 #. module: product_serial
111@@ -51,16 +82,17 @@
112 msgstr ""
113
114 #. module: product_serial
115-#: view:stock.move:0
116-msgid "Manual Split"
117-msgstr ""
118-
119-#. module: product_serial
120 #: model:ir.model,name:product_serial.model_stock_production_lot
121 msgid "Serial Number"
122 msgstr ""
123
124 #. module: product_serial
125+#: code:addons/product_serial/wizard/prodlot_wizard.py:134
126+#, python-format
127+msgid "The First and Last Numbers should be strictly positive."
128+msgstr ""
129+
130+#. module: product_serial
131 #: model:ir.model,name:product_serial.model_stock_picking
132 msgid "Picking List"
133 msgstr ""
134@@ -76,6 +108,25 @@
135 msgstr ""
136
137 #. module: product_serial
138+#: field:stock.picking.prodlot.selection,prodlot_file:0
139+msgid "Serial Numbers File"
140+msgstr ""
141+
142+#. module: product_serial
143+#: code:addons/product_serial/wizard/prodlot_wizard.py:143
144+#, python-format
145+msgid "Invalid Lot Numbers"
146+msgstr ""
147+
148+#. module: product_serial
149+#: code:addons/product_serial/wizard/prodlot_wizard.py:184
150+#: code:addons/product_serial/wizard/prodlot_wizard.py:194
151+#: code:addons/product_serial/wizard/prodlot_wizard.py:200
152+#, python-format
153+msgid "Invalid Serial Number"
154+msgstr ""
155+
156+#. module: product_serial
157 #: field:stock.picking.prodlot.selection,first_number:0
158 msgid "First Number"
159 msgstr ""
160@@ -87,8 +138,32 @@
161 msgstr ""
162
163 #. module: product_serial
164+#: code:addons/product_serial/stock.py:309
165+#, python-format
166+msgid "Error !"
167+msgstr ""
168+
169+#. module: product_serial
170+#: code:addons/product_serial/wizard/prodlot_wizard.py:185
171+#, python-format
172+msgid "Serial Number %s not found."
173+msgstr ""
174+
175+#. module: product_serial
176 #: view:stock.move:0
177-msgid "bottom"
178+msgid "Auto-Split"
179+msgstr ""
180+
181+#. module: product_serial
182+#: code:addons/product_serial/wizard/prodlot_wizard.py:128
183+#, python-format
184+msgid "Invalid Last Number"
185+msgstr ""
186+
187+#. module: product_serial
188+#: code:addons/product_serial/stock.py:309
189+#, python-format
190+msgid "Operator %s not supported in searches for last_location_id (product.product)."
191 msgstr ""
192
193 #. module: product_serial
194@@ -97,42 +172,71 @@
195 msgstr ""
196
197 #. module: product_serial
198+#: view:stock.picking.prodlot.selection:0
199+msgid "From Interval"
200+msgstr ""
201+
202+#. module: product_serial
203 #: field:stock.move,new_prodlot_code:0
204 msgid "Create Serial Number"
205 msgstr ""
206
207 #. module: product_serial
208+#: code:addons/product_serial/wizard/prodlot_wizard.py:133
209+#: code:addons/product_serial/wizard/prodlot_wizard.py:138
210+#, python-format
211+msgid "Invalid Numbers"
212+msgstr ""
213+
214+#. module: product_serial
215 #: field:res.company,autosplit_is_active:0
216 msgid "Active auto split"
217 msgstr ""
218
219 #. module: product_serial
220+#: code:addons/product_serial/wizard/prodlot_wizard.py:139
221+#, python-format
222+msgid "The First Number must be lower than the Last Number."
223+msgstr ""
224+
225+#. module: product_serial
226 #: field:res.company,is_group_invoice_line:0
227 msgid "Group invoice lines"
228 msgstr ""
229
230 #. module: product_serial
231-#: model:ir.actions.act_window,name:product_serial.action_prodlot_selection
232-#: view:stock.picking.prodlot.selection:0
233-msgid "Select Production Lots"
234-msgstr ""
235-
236-#. module: product_serial
237-#: view:stock.picking.prodlot.selection:0
238-msgid "Validate"
239-msgstr ""
240-
241-#. module: product_serial
242 #: view:stock.move:0
243 msgid "onchange_lot_id(prodlot_id, product_qty, location_id, product_id, product_uom)"
244 msgstr ""
245
246 #. module: product_serial
247+#: view:stock.picking.prodlot.selection:0
248+msgid "Select or Create Production Lots"
249+msgstr ""
250+
251+#. module: product_serial
252+#: code:addons/product_serial/stock.py:146
253+#, python-format
254+msgid "Error :"
255+msgstr ""
256+
257+#. module: product_serial
258 #: selection:product.product,lot_split_type:0
259 msgid "Logistical Unit"
260 msgstr ""
261
262 #. module: product_serial
263+#: selection:product.product,lot_split_type:0
264+msgid "None"
265+msgstr ""
266+
267+#. module: product_serial
268+#: code:addons/product_serial/wizard/prodlot_wizard.py:201
269+#, python-format
270+msgid "Not enough stock available of serial number %s."
271+msgstr ""
272+
273+#. module: product_serial
274 #: model:ir.model,name:product_serial.model_res_company
275 msgid "Companies"
276 msgstr ""
277@@ -143,8 +247,9 @@
278 msgstr ""
279
280 #. module: product_serial
281-#: help:res.company,is_group_invoice_line:0
282-msgid "If active, OpenERP will group the identical invoice lines when generating an invoice from a picking. If inactive, each move line will generate one invoice line."
283+#: code:addons/product_serial/wizard/prodlot_wizard.py:195
284+#, python-format
285+msgid "Serial Number %s exists but not for product %s."
286 msgstr ""
287
288 #. module: product_serial
289@@ -153,10 +258,30 @@
290 msgstr ""
291
292 #. module: product_serial
293-#: view:stock.picking:0
294-#: view:stock.picking.in:0
295-#: view:stock.picking.out:0
296-msgid "Spread Production Lots"
297+#: code:addons/product_serial/wizard/prodlot_wizard.py:91
298+#, python-format
299+msgid "You should upload a text file containing the serial numbers."
300+msgstr ""
301+
302+#. module: product_serial
303+#: model:ir.actions.act_window,name:product_serial.action_prodlot_selection
304+msgid "Select or Create Serial Numbers"
305+msgstr ""
306+
307+#. module: product_serial
308+#: code:addons/product_serial/wizard/prodlot_wizard.py:90
309+#, python-format
310+msgid "Error"
311+msgstr ""
312+
313+#. module: product_serial
314+#: help:product.product,track_internal:0
315+msgid "Forces to specify a Serial Number for all internal moves"
316+msgstr ""
317+
318+#. module: product_serial
319+#: view:stock.picking.prodlot.selection:0
320+msgid "From File"
321 msgstr ""
322
323 #. module: product_serial
324@@ -165,8 +290,8 @@
325 msgstr ""
326
327 #. module: product_serial
328-#: view:stock.move:0
329-msgid "onchange_product_id(product_id,location_id,location_dest_id, False)"
330+#: field:stock.production.lot,last_location_id:0
331+msgid "Last location"
332 msgstr ""
333
334 #. module: product_serial
335@@ -175,17 +300,49 @@
336 msgstr ""
337
338 #. module: product_serial
339+#: code:addons/product_serial/wizard/prodlot_wizard.py:115
340+#, python-format
341+msgid "You should enter a value for the First Number and the Last Number"
342+msgstr ""
343+
344+#. module: product_serial
345 #: view:res.company:0
346 msgid "Product serial"
347 msgstr ""
348
349 #. module: product_serial
350+#: model:ir.model,name:product_serial.model_stock_picking_prodlot_selection
351+msgid "stock.picking.prodlot.selection"
352+msgstr ""
353+
354+#. module: product_serial
355 #: model:ir.model,name:product_serial.model_stock_move
356 msgid "Stock Move"
357 msgstr ""
358
359 #. module: product_serial
360 #: view:stock.move:0
361+msgid "bottom"
362+msgstr ""
363+
364+#. module: product_serial
365+#: code:addons/product_serial/wizard/prodlot_wizard.py:121
366+#, python-format
367+msgid "Invalid First Number"
368+msgstr ""
369+
370+#. module: product_serial
371+#: help:stock.picking.prodlot.selection,prodlot_file:0
372+msgid "The serial numbers file should be a text file with one line per serial number (all for the same product)."
373+msgstr ""
374+
375+#. module: product_serial
376+#: view:stock.picking.prodlot.selection:0
377+msgid "Run From File"
378+msgstr ""
379+
380+#. module: product_serial
381+#: view:stock.move:0
382 msgid "Manual split"
383 msgstr ""
384
385@@ -203,4 +360,15 @@
386 #: model:ir.actions.act_window,name:product_serial.act_prodlot_location_open
387 msgid "Prodlots"
388 msgstr ""
389-
390+
391+#. module: product_serial
392+#: code:addons/product_serial/wizard/prodlot_wizard.py:114
393+#, python-format
394+msgid "Missing parameters"
395+msgstr ""
396+
397+#. module: product_serial
398+#: selection:product.product,lot_split_type:0
399+msgid "Single"
400+msgstr ""
401+
402
403=== modified file 'product_serial/product_demo.xml'
404--- product_serial/product_demo.xml 2013-05-11 16:09:05 +0000
405+++ product_serial/product_demo.xml 2015-12-04 10:45:32 +0000
406@@ -16,6 +16,7 @@
407 <field name="track_production">True</field>
408 <field name="track_incoming">True</field>
409 <field name="track_outgoing">True</field>
410+ <field name="track_internal">True</field>
411 </record>
412
413 </data>
414
415=== modified file 'product_serial/stock.py'
416--- product_serial/stock.py 2013-11-29 15:25:53 +0000
417+++ product_serial/stock.py 2015-12-04 10:45:32 +0000
418@@ -46,6 +46,12 @@
419 res[move.id] = move.prodlot_id and move.prodlot_id.name or False
420 return res
421
422+ def _prepare_prodlot_generation(self, cr, uid, prodlot_name, move, context=None):
423+ return {
424+ 'name': prodlot_name,
425+ 'product_id': move.product_id.id,
426+ }
427+
428 def _set_prodlot_code(self, cr, uid, ids, name, value, arg, context=None):
429 if not value: return False
430
431@@ -53,15 +59,14 @@
432 ids = [ids]
433
434 for move in self.browse(cr, uid, ids, context=context):
435- product_id = move.product_id.id
436 existing_prodlot = move.prodlot_id
437 if existing_prodlot: #avoid creating a prodlot twice
438 self.pool.get('stock.production.lot').write(cr, uid, existing_prodlot.id, {'name': value})
439 else:
440- prodlot_id = self.pool.get('stock.production.lot').create(cr, uid, {
441- 'name': value,
442- 'product_id': product_id,
443- })
444+ plot_vals = self._prepare_prodlot_generation(
445+ cr, uid, value, move, context=context)
446+ prodlot_id = self.pool.get('stock.production.lot').create(
447+ cr, uid, plot_vals, context=context)
448 move.write({'prodlot_id' : prodlot_id})
449
450 def _get_tracking_code(self, cr, uid, ids, field_name, arg, context=None):
451@@ -194,7 +199,7 @@
452
453 return result
454
455- # Because stock move line can be splitted by the module, we merge
456+ # Because stock move line can be split by the module, we merge
457 # invoice lines (if option 'is_group_invoice_line' is activated for the company)
458 # at the following conditions :
459 # - the product is the same and
460
461=== modified file 'product_serial/stock_view.xml'
462--- product_serial/stock_view.xml 2013-10-09 09:54:20 +0000
463+++ product_serial/stock_view.xml 2015-12-04 10:45:32 +0000
464@@ -32,11 +32,11 @@
465 <field name="product_packaging" groups="product.group_stock_packaging" domain="[('product_id','=',product_id)]" invisible="context.get('picking_type') != 'out' or False"/>
466 <field name="location_id" groups="stock.group_locations"/>
467 </xpath>
468- <xpath expr="//button[@name='%(stock.track_line)d']" position="replace">
469- <button name="split_move" string="Manual Split"
470+ <xpath expr="//button[@name='%(stock.track_line)d']" position="after">
471+ <button name="split_move" string="Auto-Split"
472 groups="stock.group_production_lot"
473 states="draft,waiting,confirmed,assigned"
474- type="object" icon="gtk-justify-fill" />
475+ type="object" icon="STOCK_COPY" />
476 </xpath>
477 <!-- In the form view of Incoming shipments, add the "new_prodlot_code" field -->
478 <xpath expr="//field[@name='prodlot_id']" position="before">
479@@ -89,83 +89,5 @@
480 <field eval="'ir.actions.act_window,%d'%act_prodlot_location_open" name="value"/>
481 </record>
482
483- <!-- Wizard to help users input production lots in batch -->
484-<!-- TODO Nan-TIc : port to v6
485- <record id="view_stock_picking_prodlot_selection" model="ir.ui.view">
486- <field name="name">stock.picking.prodlot.selection</field>
487- <field name="model">stock.picking.prodlot.selection</field>
488- <field name="type">form</field>
489- <field name="arch" type="xml">
490- <form string="Select Production Lots">
491- <field name="product_id" colspan="4"/>
492- <field name="first_lot"/>
493- <field name="last_lot"/>
494- <button type="object" name="action_cancel" string="Cancel" icon="gtk-cancel" special="cancel" colspan="2"/>
495- <button type="object" name="action_accept" string="Accept" icon="gtk-ok" colspan="2"/>
496- </form>
497- </field>
498- </record>
499-
500- <record model="ir.actions.act_window" id="action_prodlot_selection">
501- <field name="name">Select Production Lots</field>
502- <field name="res_model">stock.picking.prodlot.selection</field>
503- <field name="view_type">form</field>
504- <field name="view_mode">form</field>
505- <field name="target">new</field>
506- </record>
507-
508- <record id="view_picking_form" model="ir.ui.view">
509- <field name="name">stock.picking.form.prodlot.selection</field>
510- <field name="model">stock.picking</field>
511- <field name="type">form</field>
512- <field name="inherit_id" ref="stock.view_picking_form"/>
513- <field name="arch" type="xml">
514- <xpath expr="/form/notebook/page/group/label[@colspan='6']" position="replace">
515- <label colspan="5"/>
516- <button type="action" name="%(action_prodlot_selection)d" string="Spread Production Lots" states="draft,confirmed,assigned"/>
517- </xpath>
518- </field>
519- </record>
520-
521- <record id="view_picking_in_form" model="ir.ui.view">
522- <field name="name">stock.picking.in.form.prodlot.selection</field>
523- <field name="model">stock.picking</field>
524- <field name="type">form</field>
525- <field name="inherit_id" ref="stock.view_picking_in_form"/>
526- <field name="arch" type="xml">
527- <xpath expr="/form/notebook/page/group/label[@colspan='5']" position="replace">
528- <label colspan="4"/>
529- <button type="action" name="%(action_prodlot_selection)d" string="Spread Production Lots" states="draft,confirmed,assigned"/>
530- </xpath>
531- </field>
532- </record>
533-
534- <record id="view_picking_out_form" model="ir.ui.view">
535- <field name="name">stock.picking.out.form</field>
536- <field name="model">stock.picking</field>
537- <field name="type">form</field>
538- <field name="inherit_id" ref="stock.view_picking_out_form"/>
539- <field name="arch" type="xml">
540- <xpath expr="/form/notebook/page/group/label[@colspan='6']" position="replace">
541- <label colspan="5"/>
542- <button type="action" name="%(action_prodlot_selection)d" string="Spread Production Lots" states="draft,confirmed,assigned"/>
543- </xpath>
544- </field>
545- </record>
546-
547- <record id="view_picking_delivery_form" model="ir.ui.view">
548- <field name="name">stock.picking.delivery.form</field>
549- <field name="model">stock.picking</field>
550- <field name="type">form</field>
551- <field name="inherit_id" ref="stock.view_picking_delivery_form"/>
552- <field name="arch" type="xml">
553- <xpath expr="/form/notebook/page/group/label[@colspan='6']" position="replace">
554- <label colspan="5"/>
555- <button type="action" name="%(action_prodlot_selection)d" string="Spread Production Lots" states="draft,confirmed,assigned"/>
556- </xpath>
557- </field>
558- </record>
559--->
560-
561 </data>
562 </openerp>
563
564=== modified file 'product_serial/wizard/__init__.py'
565--- product_serial/wizard/__init__.py 2013-08-01 12:40:27 +0000
566+++ product_serial/wizard/__init__.py 2015-12-04 10:45:32 +0000
567@@ -20,5 +20,4 @@
568 #
569 ##############################################################################
570
571-import prodlot_wizard
572-
573+from . import prodlot_wizard
574
575=== modified file 'product_serial/wizard/prodlot_wizard.py'
576--- product_serial/wizard/prodlot_wizard.py 2013-08-01 12:40:27 +0000
577+++ product_serial/wizard/prodlot_wizard.py 2015-12-04 10:45:32 +0000
578@@ -24,95 +24,186 @@
579
580 from openerp.osv import orm, fields
581 from openerp.tools.translate import _
582+from openerp.tools.safe_eval import safe_eval
583+import base64
584
585
586 class stock_picking_prodlot_selection_wizard(orm.TransientModel):
587 _name = 'stock.picking.prodlot.selection'
588+ _description = "Select or Create Production Lots"
589
590 _columns = {
591- 'product_id': fields.many2one('product.product', 'Product', required=True),
592+ 'product_id': fields.many2one(
593+ 'product.product', 'Product', required=True),
594 'prefix': fields.char('Prefix', size=256),
595 'suffix': fields.char('Suffix', size=256),
596- 'first_number': fields.char('First Number', size=256, required=True),
597- 'last_number': fields.char('Last Number', size=256, required=True),
598+ 'first_number': fields.char('First Number', size=256),
599+ 'last_number': fields.char('Last Number', size=256),
600+ 'prodlot_file': fields.binary(
601+ 'Serial Numbers File',
602+ help="The serial numbers file should be a text file with one line "
603+ "per serial number (all for the same product)."),
604 'create_prodlots': fields.boolean('Create New Serial Numbers'),
605 }
606
607+ def _default_create_prodlots(self, cr, uid, context=None):
608+ if context is None:
609+ context = {}
610+ return context.get('default_create_prodlots')
611+
612 _defaults = {
613- 'create_prodlots': False,
614+ 'create_prodlots': _default_create_prodlots,
615 }
616
617-
618- def select_or_create_prodlots(self, cr, uid, ids, context=None):
619+ def _get_company_and_stock_moves(self, cr, uid, context=None):
620+ '''This function is designed to be generic, in order to work
621+ on the objects stock.picking (product_serial module) and
622+ mrp.production (module product_serial_mrp)'''
623 if context is None:
624 context = {}
625- if not ids:
626- return {}
627- if not 'active_id' in context:
628- return {}
629-
630- prodlot_obj = self.pool['stock.production.lot']
631+ assert ('active_id' in context), "Missing 'active_id' key in context"
632+ assert ('active_model' in context), \
633+ "Missing 'active_model' key in context"
634+ assert ('stock_move_field' in context), \
635+ "Missing 'stock_move_field' key in context"
636+
637+ active_id = context['active_id']
638+ assert isinstance(active_id, int), \
639+ "Wrong value for 'active_id' in context"
640+ parent_obj = self.pool[context['active_model']]
641+ parent_rec = parent_obj.browse(cr, uid, active_id, context=context)
642+ company_id = parent_rec.company_id.id
643+ stock_moves = safe_eval(
644+ 'parent.%s' % context['stock_move_field'],
645+ {'parent': parent_rec})
646+ return company_id, stock_moves
647+
648+ def select_or_create_prodlots_from_file(
649+ self, cr, uid, ids, context=None):
650+ assert len(ids) == 1, "Only one ID"
651+ if not ids:
652+ return {}
653+
654+ record = self.browse(cr, uid, ids[0], context)
655+ if not record.prodlot_file:
656+ raise orm.except_orm(
657+ _('Error'),
658+ _("You should upload a text file containing the serial "
659+ "numbers."))
660+ full_prodlot_str = base64.decodestring(record.prodlot_file)
661+ full_prodlot_seq = full_prodlot_str.splitlines()
662+ # Remove empty lines
663+ prodlot_seq = [prodlot for prodlot in full_prodlot_seq if prodlot]
664+ company_id, stock_moves = self._get_company_and_stock_moves(
665+ cr, uid, context=context)
666+ return self._select_or_create_prodlots(
667+ cr, uid, company_id, stock_moves, record.product_id, prodlot_seq,
668+ record.create_prodlots, context=context)
669+
670+ def select_or_create_prodlots_from_interval(
671+ self, cr, uid, ids, context=None):
672+ assert len(ids) == 1, "Only one ID"
673+ if not ids:
674+ return {}
675+
676 record = self.browse(cr, uid, ids[0], context)
677 prefix = record.prefix or ''
678 suffix = record.suffix or ''
679+ if not record.first_number or not record.last_number:
680+ raise orm.except_orm(
681+ _('Missing parameters'),
682+ _("You should enter a value for the First Number and the "
683+ "Last Number"))
684 try:
685 first_number = int(record.first_number)
686 except:
687- raise orm.except_orm(_('Invalid First Number'), _("The field 'First Number' should only contain digits."))
688+ raise orm.except_orm(
689+ _('Invalid First Number'),
690+ _("The field 'First Number' should only contain digits."))
691
692 try:
693 last_number = int(record.last_number)
694 except:
695- raise orm.except_orm(_('Invalid Last Number'), _("The field 'Last Number' should only contain digits."))
696+ raise orm.except_orm(
697+ _('Invalid Last Number'),
698+ _("The field 'Last Number' should only contain digits."))
699+
700+ if int(record.first_number) <= 0 or int(record.last_number) <= 0:
701+ raise orm.except_orm(
702+ _('Invalid Numbers'),
703+ _("The First and Last Numbers should be strictly positive."))
704
705 if last_number < first_number:
706- raise orm.except_orm(_('Invalid Numbers'), _('The First Number must be lower than the Last Number.'))
707+ raise orm.except_orm(
708+ _('Invalid Numbers'),
709+ _('The First Number must be lower than the Last Number.'))
710
711 if len(record.first_number) != len(record.last_number):
712- raise orm.except_orm(_('Invalid Lot Numbers'), _('First and Last Numbers must have the same length.'))
713+ raise orm.except_orm(
714+ _('Invalid Lot Numbers'),
715+ _('First and Last Numbers must have the same length.'))
716
717 number_length = len(record.first_number)
718+ prodlot_seq = [
719+ '%s%0*d%s' % (prefix, number_length, current_number, suffix)
720+ for current_number in range(first_number, last_number+1)
721+ ]
722+ company_id, stock_moves = self._get_company_and_stock_moves(
723+ cr, uid, context=context)
724+ return self._select_or_create_prodlots(
725+ cr, uid, company_id, stock_moves, record.product_id, prodlot_seq,
726+ record.create_prodlots, context=context)
727
728- picking_id = context['active_id']
729- current_number = first_number
730- picking = self.pool['stock.picking'].browse(cr, uid, picking_id, context=context)
731- company_id = picking.company_id.id
732- for move in picking.move_lines:
733- if move.prodlot_id or move.product_id != record.product_id:
734+ def _select_or_create_prodlots(
735+ self, cr, uid, company_id, stock_moves, product, prodlot_seq,
736+ create_prodlots, context=None):
737+ assert prodlot_seq and isinstance(prodlot_seq, list)
738+ prodlot_obj = self.pool['stock.production.lot']
739+ for move in stock_moves:
740+ if move.prodlot_id or move.product_id != product:
741 continue
742-
743- current_lot = '%%s%%0%dd%%s' % number_length % (prefix, current_number, suffix)
744- if record.create_prodlots:
745+ try:
746+ current_lot = prodlot_seq.pop(0)
747+ except:
748+ break
749+ if create_prodlots:
750 # Create new prodlot
751- lot_id_on_move = prodlot_obj.create(cr, uid, {
752- 'product_id': record.product_id.id,
753- 'name': current_lot,
754- 'company_id': company_id,
755- }, context=context)
756+ lot_id_on_move = prodlot_obj.create(
757+ cr, uid, {
758+ 'product_id': product.id,
759+ 'name': current_lot,
760+ 'company_id': company_id,
761+ }, context=context)
762 else:
763 # Search existing prodlots
764- lot_ids = prodlot_obj.search(cr, uid, [('name','=',current_lot)], limit=1, context=context)
765+ lot_ids = prodlot_obj.search(
766+ cr, uid, [('name', '=', current_lot)], limit=1,
767+ context=context)
768 if not lot_ids:
769- raise orm.except_orm(_('Invalid lot numbers'), _('Production lot %s not found.') % current_lot)
770+ raise orm.except_orm(
771+ _('Invalid Serial Number'),
772+ _('Serial Number %s not found.') % current_lot)
773
774 ctx = context.copy()
775 ctx['location_id'] = move.location_id.id
776- prodlot = self.pool.get('stock.production.lot').browse(cr, uid, lot_ids[0], ctx)
777+ prodlot = self.pool.get('stock.production.lot').browse(
778+ cr, uid, lot_ids[0], ctx)
779
780- if prodlot.product_id != record.product_id:
781- raise orm.except_orm(_('Invalid lot numbers'), _('Production lot %s exists but not for product %s.') % (current_lot, record.product_id.name))
782+ if prodlot.product_id != product:
783+ raise orm.except_orm(
784+ _('Invalid Serial Number'),
785+ _('Serial Number %s exists but not for product %s.')
786+ % (current_lot, product.name))
787
788 if prodlot.stock_available < move.product_qty:
789- raise orm.except_orm(_('Invalid lot numbers'), _('Not enough stock available of production lot %s.') % current_lot)
790+ raise orm.except_orm(
791+ _('Invalid Serial Number'),
792+ _('Not enough stock available of serial number %s.')
793+ % current_lot)
794 lot_id_on_move = lot_ids[0]
795
796- self.pool.get('stock.move').write(cr, uid, [move.id], {
797- 'prodlot_id': lot_id_on_move,
798- }, context=context)
799-
800- current_number += 1
801- if current_number > last_number:
802- break
803+ self.pool.get('stock.move').write(
804+ cr, uid, [move.id], {'prodlot_id': lot_id_on_move},
805+ context=context)
806
807 return True
808-
809
810=== modified file 'product_serial/wizard/prodlot_wizard_view.xml'
811--- product_serial/wizard/prodlot_wizard_view.xml 2013-08-01 12:40:27 +0000
812+++ product_serial/wizard/prodlot_wizard_view.xml 2015-12-04 10:45:32 +0000
813@@ -7,17 +7,23 @@
814 <field name="name">stock.picking.prodlot.selection</field>
815 <field name="model">stock.picking.prodlot.selection</field>
816 <field name="arch" type="xml">
817- <form string="Select Production Lots" version="7.0">
818- <group>
819+ <form string="Select or Create Production Lots" version="7.0">
820+ <group name="common">
821 <field name="product_id" />
822+ <field name="create_prodlots"/>
823+ </group>
824+ <group name="from_interval" string="From Interval">
825 <field name="prefix"/>
826 <field name="first_number"/>
827 <field name="last_number"/>
828 <field name="suffix"/>
829- <field name="create_prodlots"/>
830+ <button type="object" name="select_or_create_prodlots_from_interval" string="Run From Interval" class="oe_highlight"/>
831+ </group>
832+ <group name="from_file" string="From File">
833+ <field name="prodlot_file"/>
834+ <button type="object" name="select_or_create_prodlots_from_file" string="Run From File" class="oe_highlight"/>
835 </group>
836 <footer>
837- <button type="object" name="select_or_create_prodlots" string="Validate" class="oe_highlight"/>
838 <button string="Cancel" special="cancel" class="oe_link"/>
839 </footer>
840 </form>
841@@ -26,7 +32,7 @@
842
843
844 <record id="action_prodlot_selection" model="ir.actions.act_window">
845- <field name="name">Select Production Lots</field>
846+ <field name="name">Select or Create Serial Numbers</field>
847 <field name="res_model">stock.picking.prodlot.selection</field>
848 <field name="view_type">form</field>
849 <field name="view_mode">form</field>
850@@ -41,7 +47,7 @@
851 <field name="arch" type="xml">
852 <xpath expr="//field[@name='origin']/.." position="after">
853 <group name="prodlot_wizard">
854- <button type="action" name="%(action_prodlot_selection)d" string="Spread Production Lots" states="draft,confirmed,assigned"/>
855+ <button type="action" name="%(action_prodlot_selection)d" string="Spread Serial Numbers" states="draft,confirmed,assigned" context="{'stock_move_field': 'move_lines'}"/>
856 </group>
857 </xpath>
858 </field>
859@@ -55,7 +61,7 @@
860 <field name="arch" type="xml">
861 <xpath expr="//field[@name='origin']/.." position="after">
862 <group name="prodlot_wizard">
863- <button type="action" name="%(action_prodlot_selection)d" string="Spread Production Lots" states="draft,confirmed,assigned"/>
864+ <button type="action" name="%(action_prodlot_selection)d" string="Spread Serial Numbers" states="draft,confirmed,assigned" context="{'stock_move_field': 'move_lines', 'default_create_prodlots': True}"/>
865 </group>
866 </xpath>
867 </field>
868@@ -69,7 +75,7 @@
869 <field name="arch" type="xml">
870 <xpath expr="//field[@name='origin']/.." position="after">
871 <group name="prodlot_wizard">
872- <button type="action" name="%(action_prodlot_selection)d" string="Spread Production Lots" states="draft,confirmed,assigned"/>
873+ <button type="action" name="%(action_prodlot_selection)d" string="Spread Serial Numbers" states="draft,confirmed,assigned" context="{'stock_move_field': 'move_lines'}"/>
874 </group>
875 </xpath>
876 </field>
877
878=== added directory 'product_serial_purchase'
879=== added file 'product_serial_purchase/__init__.py'
880--- product_serial_purchase/__init__.py 1970-01-01 00:00:00 +0000
881+++ product_serial_purchase/__init__.py 2015-12-04 10:45:32 +0000
882@@ -0,0 +1,23 @@
883+# -*- encoding: utf-8 -*-
884+##############################################################################
885+#
886+# Product Serial Purchase module for OpenERP
887+# Copyright (C) 2013-2014 Akretion (http://www.akretion.com)
888+# @author Alexis de Lattre <alexis.delattre@akretion.com>
889+#
890+# This program is free software: you can redistribute it and/or modify
891+# it under the terms of the GNU Affero General Public License as
892+# published by the Free Software Foundation, either version 3 of the
893+# License, or (at your option) any later version.
894+#
895+# This program is distributed in the hope that it will be useful,
896+# but WITHOUT ANY WARRANTY; without even the implied warranty of
897+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
898+# GNU Affero General Public License for more details.
899+#
900+# You should have received a copy of the GNU Affero General Public License
901+# along with this program. If not, see <http://www.gnu.org/licenses/>.
902+#
903+##############################################################################
904+
905+from . import purchase
906
907=== added file 'product_serial_purchase/__openerp__.py'
908--- product_serial_purchase/__openerp__.py 1970-01-01 00:00:00 +0000
909+++ product_serial_purchase/__openerp__.py 2015-12-04 10:45:32 +0000
910@@ -0,0 +1,44 @@
911+# -*- encoding: utf-8 -*-
912+##############################################################################
913+#
914+# Product Serial Purchase module for OpenERP
915+# Copyright (C) 2013-2014 Akretion (http://www.akretion.com)
916+# @author Alexis de Lattre <alexis.delattre@akretion.com>
917+#
918+# This program is free software: you can redistribute it and/or modify
919+# it under the terms of the GNU Affero General Public License as
920+# published by the Free Software Foundation, either version 3 of the
921+# License, or (at your option) any later version.
922+#
923+# This program is distributed in the hope that it will be useful,
924+# but WITHOUT ANY WARRANTY; without even the implied warranty of
925+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
926+# GNU Affero General Public License for more details.
927+#
928+# You should have received a copy of the GNU Affero General Public License
929+# along with this program. If not, see <http://www.gnu.org/licenses/>.
930+#
931+##############################################################################
932+
933+
934+{
935+ 'name': 'Product Serial Purchase',
936+ 'version': '0.1',
937+ 'category': 'Inventory, Logistic, Storage',
938+ 'license': 'AGPL-3',
939+ 'description': """
940+Product Serial Purchase
941+=======================
942+
943+The module *product_serial* only depends on the module *stock*, not on the module *purchase*. If you only have the module *product_serial* and you activated the grouping of purchase order lines (via the module purchase_auto_merge for example), the move lines of the incoming picking will be automatically split but the *move_dest_id* of the generated move lines will all be the same. As a consequence, only one move line of the delivery order will be set to 'Available' ; the others will be stuck on 'Waiting Availability'. This module correctly handles the *move_dest_id* on the move lines of the incoming picking.
944+
945+Please contact Alexis de Lattre from Akretion <alexis.delattre@akretion.com> for any help or question about this module.
946+ """,
947+ 'author': 'Akretion',
948+ 'website': 'http://www.akretion.com',
949+ 'depends': ['purchase', 'product_serial'],
950+ 'data': [],
951+ 'images': [],
952+ 'installable': True,
953+ 'active': False,
954+}
955
956=== added directory 'product_serial_purchase/i18n'
957=== added file 'product_serial_purchase/i18n/product_serial_purchase.pot'
958--- product_serial_purchase/i18n/product_serial_purchase.pot 1970-01-01 00:00:00 +0000
959+++ product_serial_purchase/i18n/product_serial_purchase.pot 2015-12-04 10:45:32 +0000
960@@ -0,0 +1,34 @@
961+# Translation of OpenERP Server.
962+# This file contains the translation of the following modules:
963+# * product_serial_purchase
964+#
965+msgid ""
966+msgstr ""
967+"Project-Id-Version: OpenERP Server 7.0\n"
968+"Report-Msgid-Bugs-To: \n"
969+"POT-Creation-Date: 2014-01-03 18:27+0000\n"
970+"PO-Revision-Date: 2014-01-03 18:27+0000\n"
971+"Last-Translator: <>\n"
972+"Language-Team: \n"
973+"MIME-Version: 1.0\n"
974+"Content-Type: text/plain; charset=UTF-8\n"
975+"Content-Transfer-Encoding: \n"
976+"Plural-Forms: \n"
977+
978+#. module: product_serial_purchase
979+#: code:addons/product_serial_purchase/purchase.py:101
980+#, python-format
981+msgid "Product '%s' has 'Lot split type' = 'Logistical Unit' but it has no packaging information."
982+msgstr ""
983+
984+#. module: product_serial_purchase
985+#: code:addons/product_serial_purchase/purchase.py:100
986+#, python-format
987+msgid "Error:"
988+msgstr ""
989+
990+#. module: product_serial_purchase
991+#: model:ir.model,name:product_serial_purchase.model_purchase_order
992+msgid "Purchase Order"
993+msgstr ""
994+
995
996=== added file 'product_serial_purchase/purchase.py'
997--- product_serial_purchase/purchase.py 1970-01-01 00:00:00 +0000
998+++ product_serial_purchase/purchase.py 2015-12-04 10:45:32 +0000
999@@ -0,0 +1,131 @@
1000+# -*- encoding: utf-8 -*-
1001+##############################################################################
1002+#
1003+# Product Serial Purchase module for OpenERP
1004+# Copyright (C) 2013-2014 Akretion (http://www.akretion.com)
1005+# Copyright (C) OpenERP S.A.
1006+# @author Alexis de Lattre <alexis.delattre@akretion.com>
1007+#
1008+# This program is free software: you can redistribute it and/or modify
1009+# it under the terms of the GNU Affero General Public License as
1010+# published by the Free Software Foundation, either version 3 of the
1011+# License, or (at your option) any later version.
1012+#
1013+# This program is distributed in the hope that it will be useful,
1014+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1015+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1016+# GNU Affero General Public License for more details.
1017+#
1018+# You should have received a copy of the GNU Affero General Public License
1019+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1020+#
1021+##############################################################################
1022+
1023+from openerp.osv import orm
1024+from openerp.tools.translate import _
1025+from openerp import netsvc
1026+
1027+
1028+class purchase_order(orm.Model):
1029+ _inherit = 'purchase.order'
1030+
1031+ def _prepare_order_line_move(
1032+ self, cr, uid, order, order_line, picking_id, context=None):
1033+ if context is None:
1034+ context = {}
1035+ vals = super(purchase_order, self)._prepare_order_line_move(
1036+ cr, uid, order, order_line, picking_id, context=context)
1037+ if context.get('force_qty'):
1038+ vals['product_qty'] = context.get('force_qty')
1039+ vals['product_uos_qty'] = context.get('force_qty')
1040+ if context.get('force_move_dest_id'):
1041+ vals['move_dest_id'] = context.get('force_move_dest_id')
1042+ return vals
1043+
1044+ def _add_stock_move_on_picking(
1045+ self, cr, uid, order, line, picking_id, qty, proc_move_dest_id,
1046+ todo_moves, context=None):
1047+ if context is None:
1048+ context = {}
1049+ move_ctx = context.copy()
1050+ move_ctx['force_qty'] = qty
1051+ for move_dest_id, proc_qty in proc_move_dest_id.items():
1052+ if proc_qty == qty:
1053+ move_ctx['force_move_dest_id'] = move_dest_id
1054+ proc_move_dest_id.pop(move_dest_id)
1055+ self.pool['stock.move'].write(
1056+ cr, uid, move_dest_id, {
1057+ 'location_id': order.location_id.id},
1058+ context=context)
1059+ break
1060+ move_id = self.pool['stock.move'].create(
1061+ cr, uid, self._prepare_order_line_move(
1062+ cr, uid, order, line, picking_id,
1063+ context=move_ctx))
1064+ todo_moves.append(move_id)
1065+ return move_id
1066+
1067+ def _create_pickings(
1068+ self, cr, uid, order, order_lines, picking_id=False, context=None):
1069+ if not picking_id:
1070+ picking_id = self.pool.get('stock.picking').create(
1071+ cr, uid, self._prepare_order_picking(
1072+ cr, uid, order, context=context))
1073+ todo_moves = []
1074+ stock_move = self.pool['stock.move']
1075+ wf_service = netsvc.LocalService("workflow")
1076+ for line in order_lines:
1077+ if not line.product_id:
1078+ continue
1079+ if line.product_id.type in ('product', 'consu'):
1080+ # We neutralise the procurement stock move now, to be sure
1081+ # that we don't miss this important task (for exemple when
1082+ # the user changes the quantity in the PO)
1083+ if (
1084+ line.move_dest_id
1085+ and line.move_dest_id.state != 'done'):
1086+ line.move_dest_id.write(
1087+ {'location_id': order.location_id.id}, context=context)
1088+
1089+ proc_ids = self.pool['procurement.order'].search(
1090+ cr, uid, [
1091+ ('purchase_line_id', '=', line.id),
1092+ ('product_id', '=', line.product_id.id),
1093+ ('state', '!=', 'cancel'),
1094+ ], context=context)
1095+ proc_move_dest_id = {}
1096+ # key = move_dest_id ; value = product_qty
1097+ for procurement in self.pool['procurement.order'].browse(
1098+ cr, uid, proc_ids, context=context):
1099+ proc_move_dest_id[procurement.move_id.id] = \
1100+ procurement.product_qty
1101+
1102+ qty = line.product_qty
1103+ if line.product_id.lot_split_type == 'lu':
1104+ if not line.product_id.packaging:
1105+ raise orm.except_orm(
1106+ _('Error:'),
1107+ _("Product '%s' has 'Lot split type' = "
1108+ "'Logistical Unit' but it has no packaging "
1109+ "information.")
1110+ % (line.product_id.name))
1111+ lu_qty = line.product_id.packaging[0].qty
1112+ elif line.product_id.lot_split_type == 'single':
1113+ lu_qty = 1
1114+ else:
1115+ lu_qty = qty
1116+ while qty >= lu_qty:
1117+ self._add_stock_move_on_picking(
1118+ cr, uid, order, line, picking_id, lu_qty,
1119+ proc_move_dest_id, todo_moves, context=context)
1120+ qty -= lu_qty
1121+ if qty > 0:
1122+ # Create last move/proc for the remaining qty
1123+ self._add_stock_move_on_picking(
1124+ cr, uid, order, line, picking_id, qty,
1125+ proc_move_dest_id, todo_moves, context=context)
1126+ stock_move.action_confirm(cr, uid, todo_moves)
1127+ stock_move.force_assign(cr, uid, todo_moves)
1128+ wf_service.trg_validate(
1129+ uid, 'stock.picking', picking_id, 'button_confirm', cr)
1130+ return [picking_id]
1131
1132=== added directory 'product_serial_sale_stock'
1133=== added file 'product_serial_sale_stock/__init__.py'
1134--- product_serial_sale_stock/__init__.py 1970-01-01 00:00:00 +0000
1135+++ product_serial_sale_stock/__init__.py 2015-12-04 10:45:32 +0000
1136@@ -0,0 +1,23 @@
1137+# -*- encoding: utf-8 -*-
1138+##############################################################################
1139+#
1140+# Product Serial Sale Stock module for OpenERP
1141+# Copyright (C) 2013 Akretion (http://www.akretion.com)
1142+# @author Alexis de Lattre <alexis.delattre@akretion.com>
1143+#
1144+# This program is free software: you can redistribute it and/or modify
1145+# it under the terms of the GNU Affero General Public License as
1146+# published by the Free Software Foundation, either version 3 of the
1147+# License, or (at your option) any later version.
1148+#
1149+# This program is distributed in the hope that it will be useful,
1150+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1151+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1152+# GNU Affero General Public License for more details.
1153+#
1154+# You should have received a copy of the GNU Affero General Public License
1155+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1156+#
1157+##############################################################################
1158+
1159+from . import sale_stock
1160
1161=== added file 'product_serial_sale_stock/__openerp__.py'
1162--- product_serial_sale_stock/__openerp__.py 1970-01-01 00:00:00 +0000
1163+++ product_serial_sale_stock/__openerp__.py 2015-12-04 10:45:32 +0000
1164@@ -0,0 +1,46 @@
1165+# -*- encoding: utf-8 -*-
1166+##############################################################################
1167+#
1168+# Product Serial Sale Stock module for OpenERP
1169+# Copyright (C) 2013 Akretion (http://www.akretion.com)
1170+# @author Alexis de Lattre <alexis.delattre@akretion.com>
1171+#
1172+# This program is free software: you can redistribute it and/or modify
1173+# it under the terms of the GNU Affero General Public License as
1174+# published by the Free Software Foundation, either version 3 of the
1175+# License, or (at your option) any later version.
1176+#
1177+# This program is distributed in the hope that it will be useful,
1178+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1179+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1180+# GNU Affero General Public License for more details.
1181+#
1182+# You should have received a copy of the GNU Affero General Public License
1183+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1184+#
1185+##############################################################################
1186+
1187+
1188+{
1189+ 'name': 'Product Serial Sale Stock',
1190+ 'version': '0.1',
1191+ 'category': 'Inventory, Logistic, Storage',
1192+ 'license': 'AGPL-3',
1193+ 'description': """
1194+Product Serial Sale Stock
1195+=========================
1196+
1197+The module *product_serial* only depends on stock, not on procurements. If you only have the module *product_serial* and the move lines are automatically split, the procurements associated with the move lines are not split, so the split stock moves don't automatically switch from *Waiting Availability* to *Ready to Transfer* when the products are available in the stock. This module handles this.
1198+
1199+This module uses the monkey-patching method to replace the code of the method _create_pickings_and_procurements() by a different code.
1200+
1201+Please contact Alexis de Lattre from Akretion <alexis.delattre@akretion.com> for any help or question about this module.
1202+ """,
1203+ 'author': 'Akretion',
1204+ 'website': 'http://www.akretion.com',
1205+ 'depends': ['product_serial', 'sale_stock'],
1206+ 'data': [],
1207+ 'images': [],
1208+ 'installable': True,
1209+ 'active': False,
1210+}
1211
1212=== added directory 'product_serial_sale_stock/i18n'
1213=== added file 'product_serial_sale_stock/i18n/product_serial_sale_stock.pot'
1214--- product_serial_sale_stock/i18n/product_serial_sale_stock.pot 1970-01-01 00:00:00 +0000
1215+++ product_serial_sale_stock/i18n/product_serial_sale_stock.pot 2015-12-04 10:45:32 +0000
1216@@ -0,0 +1,34 @@
1217+# Translation of OpenERP Server.
1218+# This file contains the translation of the following modules:
1219+# * product_serial_sale_stock
1220+#
1221+msgid ""
1222+msgstr ""
1223+"Project-Id-Version: OpenERP Server 7.0\n"
1224+"Report-Msgid-Bugs-To: \n"
1225+"POT-Creation-Date: 2014-01-03 18:28+0000\n"
1226+"PO-Revision-Date: 2014-01-03 18:28+0000\n"
1227+"Last-Translator: <>\n"
1228+"Language-Team: \n"
1229+"MIME-Version: 1.0\n"
1230+"Content-Type: text/plain; charset=UTF-8\n"
1231+"Content-Transfer-Encoding: \n"
1232+"Plural-Forms: \n"
1233+
1234+#. module: product_serial_sale_stock
1235+#: code:addons/product_serial_sale_stock/sale_stock.py:100
1236+#, python-format
1237+msgid "Product '%s' has 'Lot split type' = 'Logistical Unit' but it has no packaging information."
1238+msgstr ""
1239+
1240+#. module: product_serial_sale_stock
1241+#: code:addons/product_serial_sale_stock/sale_stock.py:99
1242+#, python-format
1243+msgid "Error:"
1244+msgstr ""
1245+
1246+#. module: product_serial_sale_stock
1247+#: model:ir.model,name:product_serial_sale_stock.model_sale_order
1248+msgid "Sales Order"
1249+msgstr ""
1250+
1251
1252=== added file 'product_serial_sale_stock/sale_stock.py'
1253--- product_serial_sale_stock/sale_stock.py 1970-01-01 00:00:00 +0000
1254+++ product_serial_sale_stock/sale_stock.py 2015-12-04 10:45:32 +0000
1255@@ -0,0 +1,173 @@
1256+# -*- encoding: utf-8 -*-
1257+##############################################################################
1258+#
1259+# Product Serial Sale Stock module for OpenERP
1260+# Copyright (C) 2013-2014 Akretion (http://www.akretion.com)
1261+# Copyright (C) OpenERP S.A.
1262+# @author Alexis de Lattre <alexis.delattre@akretion.com>
1263+#
1264+# This program is free software: you can redistribute it and/or modify
1265+# it under the terms of the GNU Affero General Public License as
1266+# published by the Free Software Foundation, either version 3 of the
1267+# License, or (at your option) any later version.
1268+#
1269+# This program is distributed in the hope that it will be useful,
1270+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1271+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1272+# GNU Affero General Public License for more details.
1273+#
1274+# You should have received a copy of the GNU Affero General Public License
1275+# along with this program. If not, see <http://www.gnu.org/licenses/>.
1276+#
1277+##############################################################################
1278+
1279+from openerp.osv import orm
1280+from openerp.tools.translate import _
1281+from openerp import netsvc
1282+# For Monkey-patching:
1283+from openerp.addons.sale_stock import sale_stock
1284+
1285+
1286+class sale_order(orm.Model):
1287+ _inherit = 'sale.order'
1288+
1289+ def _prepare_order_line_procurement(
1290+ self, cr, uid, order, line, move_id, date_planned, context=None):
1291+ if context is None:
1292+ context = {}
1293+ vals = super(sale_order, self)._prepare_order_line_procurement(
1294+ cr, uid, order, line, move_id, date_planned, context=context)
1295+ if context.get('force_qty'):
1296+ vals['product_qty'] = context.get('force_qty')
1297+ vals['product_uos_qty'] = context.get('force_qty')
1298+ return vals
1299+
1300+ def _prepare_order_line_move(
1301+ self, cr, uid, order, line, picking_id, date_planned,
1302+ context=None):
1303+ if context is None:
1304+ context = {}
1305+ vals = super(sale_order, self)._prepare_order_line_move(
1306+ cr, uid, order, line, picking_id, date_planned, context=context)
1307+ if context.get('force_qty'):
1308+ vals['product_qty'] = context.get('force_qty')
1309+ vals['product_uos_qty'] = context.get('force_qty')
1310+ return vals
1311+
1312+ def _create_move_line_and_procurement(
1313+ self, cr, uid, order, line, picking_id, qty, proc_ids,
1314+ date_planned, context=None):
1315+ if context is None:
1316+ context = {}
1317+ qty_ctx = context.copy()
1318+ qty_ctx['force_qty'] = qty
1319+ picking_obj = self.pool.get('stock.picking')
1320+ move_obj = self.pool.get('stock.move')
1321+ procurement_obj = self.pool.get('procurement.order')
1322+ if line.product_id.type in ('product', 'consu'):
1323+ if not picking_id:
1324+ picking_id = picking_obj.create(
1325+ cr, uid, self._prepare_order_picking(
1326+ cr, uid, order, context=context),
1327+ context=context)
1328+ move_id = move_obj.create(
1329+ cr, uid, self._prepare_order_line_move(
1330+ cr, uid, order, line, picking_id, date_planned,
1331+ context=qty_ctx),
1332+ context=context)
1333+ else:
1334+ # a service has no stock move
1335+ move_id = False
1336+
1337+ proc_id = procurement_obj.create(
1338+ cr, uid, self._prepare_order_line_procurement(
1339+ cr, uid, order, line, move_id, date_planned,
1340+ context=qty_ctx),
1341+ context=context)
1342+ proc_ids.append(proc_id)
1343+ return picking_id
1344+
1345+
1346+class product_serial_sale_stock_installed(orm.AbstractModel):
1347+ _name = "product.serial.sale.stock.installed"
1348+ # This class is used only to test if the module is installed or not
1349+
1350+
1351+original_create_pickings_and_procurements = \
1352+ sale_stock.sale_order._create_pickings_and_procurements
1353+
1354+
1355+def _create_pickings_and_procurements(
1356+ self, cr, uid, order, order_lines, picking_id=False, context=None):
1357+ if self.pool.get('product.serial.sale.stock.installed'):
1358+ # Here is the code that is executed when the module
1359+ # product_serial_sale_stock is installed
1360+ if context is None:
1361+ context = {}
1362+ proc_ids = []
1363+
1364+ for line in order_lines:
1365+ if line.state == 'done':
1366+ continue
1367+
1368+ date_planned = self._get_date_planned(
1369+ cr, uid, order, line, order.date_order, context=context)
1370+
1371+ if line.product_id and line.product_uom_qty:
1372+ qty = line.product_uom_qty
1373+ if line.product_id.lot_split_type == 'lu':
1374+ if not line.product_id.packaging:
1375+ raise orm.except_orm(
1376+ _('Error:'),
1377+ _("Product '%s' has 'Lot split type' = "
1378+ "'Logistical Unit' but it has no packaging "
1379+ "information.")
1380+ % (line.product_id.name))
1381+ lu_qty = line.product_id.packaging[0].qty
1382+ elif line.product_id.lot_split_type == 'single':
1383+ lu_qty = 1
1384+ else:
1385+ lu_qty = qty
1386+ while qty >= lu_qty:
1387+ picking_id = self._create_move_line_and_procurement(
1388+ cr, uid, order, line, picking_id, lu_qty, proc_ids,
1389+ date_planned, context=context)
1390+ qty -= lu_qty
1391+ if qty > 0:
1392+ # Create last move/proc for the remaining qty
1393+ picking_id = self._create_move_line_and_procurement(
1394+ cr, uid, order, line, picking_id, qty, proc_ids,
1395+ date_planned, context=context)
1396+ line.write({'procurement_id': proc_ids[-1]})
1397+ #self.ship_recreate(cr, uid, order, line, move_id, proc_id)
1398+
1399+ wf_service = netsvc.LocalService("workflow")
1400+ if picking_id:
1401+ wf_service.trg_validate(
1402+ uid, 'stock.picking', picking_id, 'button_confirm', cr)
1403+ for proc_id in proc_ids:
1404+ wf_service.trg_validate(
1405+ uid, 'procurement.order', proc_id, 'button_confirm', cr)
1406+
1407+ val = {}
1408+ if order.state == 'shipping_except':
1409+ val['state'] = 'progress'
1410+ val['shipped'] = False
1411+
1412+ if order.order_policy == 'manual':
1413+ for line in order.order_line:
1414+ if not line.invoiced and \
1415+ line.state not in ('cancel', 'draft'):
1416+ val['state'] = 'manual'
1417+ break
1418+ order.write(val)
1419+ return True
1420+ else:
1421+ # This is the code that is executed when the module
1422+ # product_serial_sale_stock is NOT installed
1423+ return original_create_pickings_and_procurements(
1424+ self, cr, uid, order, order_lines, picking_id=picking_id,
1425+ context=context)
1426+
1427+sale_stock.sale_order._create_pickings_and_procurements = \
1428+ _create_pickings_and_procurements

Subscribers

People subscribed via source and target branches