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
=== modified file 'product_serial/__init__.py'
--- product_serial/__init__.py 2013-08-01 12:40:27 +0000
+++ product_serial/__init__.py 2015-12-04 10:45:32 +0000
@@ -19,8 +19,7 @@
19#19#
20##############################################################################20##############################################################################
2121
22import product22from . import product
23import stock23from . import stock
24import company24from . import company
25import wizard25from . import wizard
26
2726
=== modified file 'product_serial/i18n/product_serial.pot'
--- product_serial/i18n/product_serial.pot 2013-11-29 15:22:57 +0000
+++ product_serial/i18n/product_serial.pot 2015-12-04 10:45:32 +0000
@@ -1,13 +1,13 @@
1# Translation of OpenERP Server.1# Translation of OpenERP Server.
2# This file contains the translation of the following modules:2# This file contains the translation of the following modules:
3# * product_serial3# * product_serial
4#4#
5msgid ""5msgid ""
6msgstr ""6msgstr ""
7"Project-Id-Version: OpenERP Server 7.0\n"7"Project-Id-Version: OpenERP Server 7.0\n"
8"Report-Msgid-Bugs-To: \n"8"Report-Msgid-Bugs-To: \n"
9"POT-Creation-Date: 2013-10-09 09:30+0000\n"9"POT-Creation-Date: 2014-03-19 13:36+0000\n"
10"PO-Revision-Date: 2013-11-29 16:22+0100\n"10"PO-Revision-Date: 2014-03-19 13:36+0000\n"
11"Last-Translator: <>\n"11"Last-Translator: <>\n"
12"Language-Team: \n"12"Language-Team: \n"
13"MIME-Version: 1.0\n"13"MIME-Version: 1.0\n"
@@ -21,8 +21,27 @@
21msgstr ""21msgstr ""
2222
23#. module: product_serial23#. module: product_serial
24#: selection:product.product,lot_split_type:024#: code:addons/product_serial/stock.py:146
25msgid "Single"25#, python-format
26msgid "Product '%s' has 'Lot split type' = 'Logistical Unit' but is missing packaging information."
27msgstr ""
28
29#. module: product_serial
30#: view:stock.picking:0
31#: view:stock.picking.in:0
32#: view:stock.picking.out:0
33msgid "Spread Serial Numbers"
34msgstr ""
35
36#. module: product_serial
37#: field:product.product,track_internal:0
38msgid "Track Lots internally"
39msgstr ""
40
41#. module: product_serial
42#: code:addons/product_serial/wizard/prodlot_wizard.py:122
43#, python-format
44msgid "The field 'First Number' should only contain digits."
26msgstr ""45msgstr ""
2746
28#. module: product_serial47#. module: product_serial
@@ -31,18 +50,30 @@
31msgstr ""50msgstr ""
3251
33#. module: product_serial52#. module: product_serial
34#: selection:product.product,lot_split_type:053#: code:addons/product_serial/wizard/prodlot_wizard.py:129
35msgid "None"54#, python-format
36msgstr ""55msgid "The field 'Last Number' should only contain digits."
3756msgstr ""
38#. module: product_serial57
39#: model:ir.model,name:product_serial.model_stock_picking_prodlot_selection58#. module: product_serial
40msgid "stock.picking.prodlot.selection"59#: help:res.company,is_group_invoice_line:0
41msgstr ""60msgid "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."
4261msgstr ""
43#. module: product_serial62
44#: field:stock.production.lot,last_location_id:063#. module: product_serial
45msgid "Last location"64#: view:stock.move:0
65msgid "onchange_product_id(product_id,location_id,location_dest_id, False)"
66msgstr ""
67
68#. module: product_serial
69#: view:stock.picking.prodlot.selection:0
70msgid "Run From Interval"
71msgstr ""
72
73#. module: product_serial
74#: code:addons/product_serial/wizard/prodlot_wizard.py:144
75#, python-format
76msgid "First and Last Numbers must have the same length."
46msgstr ""77msgstr ""
4778
48#. module: product_serial79#. module: product_serial
@@ -51,16 +82,17 @@
51msgstr ""82msgstr ""
5283
53#. module: product_serial84#. module: product_serial
54#: view:stock.move:0
55msgid "Manual Split"
56msgstr ""
57
58#. module: product_serial
59#: model:ir.model,name:product_serial.model_stock_production_lot85#: model:ir.model,name:product_serial.model_stock_production_lot
60msgid "Serial Number"86msgid "Serial Number"
61msgstr ""87msgstr ""
6288
63#. module: product_serial89#. module: product_serial
90#: code:addons/product_serial/wizard/prodlot_wizard.py:134
91#, python-format
92msgid "The First and Last Numbers should be strictly positive."
93msgstr ""
94
95#. module: product_serial
64#: model:ir.model,name:product_serial.model_stock_picking96#: model:ir.model,name:product_serial.model_stock_picking
65msgid "Picking List"97msgid "Picking List"
66msgstr ""98msgstr ""
@@ -76,6 +108,25 @@
76msgstr ""108msgstr ""
77109
78#. module: product_serial110#. module: product_serial
111#: field:stock.picking.prodlot.selection,prodlot_file:0
112msgid "Serial Numbers File"
113msgstr ""
114
115#. module: product_serial
116#: code:addons/product_serial/wizard/prodlot_wizard.py:143
117#, python-format
118msgid "Invalid Lot Numbers"
119msgstr ""
120
121#. module: product_serial
122#: code:addons/product_serial/wizard/prodlot_wizard.py:184
123#: code:addons/product_serial/wizard/prodlot_wizard.py:194
124#: code:addons/product_serial/wizard/prodlot_wizard.py:200
125#, python-format
126msgid "Invalid Serial Number"
127msgstr ""
128
129#. module: product_serial
79#: field:stock.picking.prodlot.selection,first_number:0130#: field:stock.picking.prodlot.selection,first_number:0
80msgid "First Number"131msgid "First Number"
81msgstr ""132msgstr ""
@@ -87,8 +138,32 @@
87msgstr ""138msgstr ""
88139
89#. module: product_serial140#. module: product_serial
141#: code:addons/product_serial/stock.py:309
142#, python-format
143msgid "Error !"
144msgstr ""
145
146#. module: product_serial
147#: code:addons/product_serial/wizard/prodlot_wizard.py:185
148#, python-format
149msgid "Serial Number %s not found."
150msgstr ""
151
152#. module: product_serial
90#: view:stock.move:0153#: view:stock.move:0
91msgid "bottom"154msgid "Auto-Split"
155msgstr ""
156
157#. module: product_serial
158#: code:addons/product_serial/wizard/prodlot_wizard.py:128
159#, python-format
160msgid "Invalid Last Number"
161msgstr ""
162
163#. module: product_serial
164#: code:addons/product_serial/stock.py:309
165#, python-format
166msgid "Operator %s not supported in searches for last_location_id (product.product)."
92msgstr ""167msgstr ""
93168
94#. module: product_serial169#. module: product_serial
@@ -97,42 +172,71 @@
97msgstr ""172msgstr ""
98173
99#. module: product_serial174#. module: product_serial
175#: view:stock.picking.prodlot.selection:0
176msgid "From Interval"
177msgstr ""
178
179#. module: product_serial
100#: field:stock.move,new_prodlot_code:0180#: field:stock.move,new_prodlot_code:0
101msgid "Create Serial Number"181msgid "Create Serial Number"
102msgstr ""182msgstr ""
103183
104#. module: product_serial184#. module: product_serial
185#: code:addons/product_serial/wizard/prodlot_wizard.py:133
186#: code:addons/product_serial/wizard/prodlot_wizard.py:138
187#, python-format
188msgid "Invalid Numbers"
189msgstr ""
190
191#. module: product_serial
105#: field:res.company,autosplit_is_active:0192#: field:res.company,autosplit_is_active:0
106msgid "Active auto split"193msgid "Active auto split"
107msgstr ""194msgstr ""
108195
109#. module: product_serial196#. module: product_serial
197#: code:addons/product_serial/wizard/prodlot_wizard.py:139
198#, python-format
199msgid "The First Number must be lower than the Last Number."
200msgstr ""
201
202#. module: product_serial
110#: field:res.company,is_group_invoice_line:0203#: field:res.company,is_group_invoice_line:0
111msgid "Group invoice lines"204msgid "Group invoice lines"
112msgstr ""205msgstr ""
113206
114#. module: product_serial207#. module: product_serial
115#: model:ir.actions.act_window,name:product_serial.action_prodlot_selection
116#: view:stock.picking.prodlot.selection:0
117msgid "Select Production Lots"
118msgstr ""
119
120#. module: product_serial
121#: view:stock.picking.prodlot.selection:0
122msgid "Validate"
123msgstr ""
124
125#. module: product_serial
126#: view:stock.move:0208#: view:stock.move:0
127msgid "onchange_lot_id(prodlot_id, product_qty, location_id, product_id, product_uom)"209msgid "onchange_lot_id(prodlot_id, product_qty, location_id, product_id, product_uom)"
128msgstr ""210msgstr ""
129211
130#. module: product_serial212#. module: product_serial
213#: view:stock.picking.prodlot.selection:0
214msgid "Select or Create Production Lots"
215msgstr ""
216
217#. module: product_serial
218#: code:addons/product_serial/stock.py:146
219#, python-format
220msgid "Error :"
221msgstr ""
222
223#. module: product_serial
131#: selection:product.product,lot_split_type:0224#: selection:product.product,lot_split_type:0
132msgid "Logistical Unit"225msgid "Logistical Unit"
133msgstr ""226msgstr ""
134227
135#. module: product_serial228#. module: product_serial
229#: selection:product.product,lot_split_type:0
230msgid "None"
231msgstr ""
232
233#. module: product_serial
234#: code:addons/product_serial/wizard/prodlot_wizard.py:201
235#, python-format
236msgid "Not enough stock available of serial number %s."
237msgstr ""
238
239#. module: product_serial
136#: model:ir.model,name:product_serial.model_res_company240#: model:ir.model,name:product_serial.model_res_company
137msgid "Companies"241msgid "Companies"
138msgstr ""242msgstr ""
@@ -143,8 +247,9 @@
143msgstr ""247msgstr ""
144248
145#. module: product_serial249#. module: product_serial
146#: help:res.company,is_group_invoice_line:0250#: code:addons/product_serial/wizard/prodlot_wizard.py:195
147msgid "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."251#, python-format
252msgid "Serial Number %s exists but not for product %s."
148msgstr ""253msgstr ""
149254
150#. module: product_serial255#. module: product_serial
@@ -153,10 +258,30 @@
153msgstr ""258msgstr ""
154259
155#. module: product_serial260#. module: product_serial
156#: view:stock.picking:0261#: code:addons/product_serial/wizard/prodlot_wizard.py:91
157#: view:stock.picking.in:0262#, python-format
158#: view:stock.picking.out:0263msgid "You should upload a text file containing the serial numbers."
159msgid "Spread Production Lots"264msgstr ""
265
266#. module: product_serial
267#: model:ir.actions.act_window,name:product_serial.action_prodlot_selection
268msgid "Select or Create Serial Numbers"
269msgstr ""
270
271#. module: product_serial
272#: code:addons/product_serial/wizard/prodlot_wizard.py:90
273#, python-format
274msgid "Error"
275msgstr ""
276
277#. module: product_serial
278#: help:product.product,track_internal:0
279msgid "Forces to specify a Serial Number for all internal moves"
280msgstr ""
281
282#. module: product_serial
283#: view:stock.picking.prodlot.selection:0
284msgid "From File"
160msgstr ""285msgstr ""
161286
162#. module: product_serial287#. module: product_serial
@@ -165,8 +290,8 @@
165msgstr ""290msgstr ""
166291
167#. module: product_serial292#. module: product_serial
168#: view:stock.move:0293#: field:stock.production.lot,last_location_id:0
169msgid "onchange_product_id(product_id,location_id,location_dest_id, False)"294msgid "Last location"
170msgstr ""295msgstr ""
171296
172#. module: product_serial297#. module: product_serial
@@ -175,17 +300,49 @@
175msgstr ""300msgstr ""
176301
177#. module: product_serial302#. module: product_serial
303#: code:addons/product_serial/wizard/prodlot_wizard.py:115
304#, python-format
305msgid "You should enter a value for the First Number and the Last Number"
306msgstr ""
307
308#. module: product_serial
178#: view:res.company:0309#: view:res.company:0
179msgid "Product serial"310msgid "Product serial"
180msgstr ""311msgstr ""
181312
182#. module: product_serial313#. module: product_serial
314#: model:ir.model,name:product_serial.model_stock_picking_prodlot_selection
315msgid "stock.picking.prodlot.selection"
316msgstr ""
317
318#. module: product_serial
183#: model:ir.model,name:product_serial.model_stock_move319#: model:ir.model,name:product_serial.model_stock_move
184msgid "Stock Move"320msgid "Stock Move"
185msgstr ""321msgstr ""
186322
187#. module: product_serial323#. module: product_serial
188#: view:stock.move:0324#: view:stock.move:0
325msgid "bottom"
326msgstr ""
327
328#. module: product_serial
329#: code:addons/product_serial/wizard/prodlot_wizard.py:121
330#, python-format
331msgid "Invalid First Number"
332msgstr ""
333
334#. module: product_serial
335#: help:stock.picking.prodlot.selection,prodlot_file:0
336msgid "The serial numbers file should be a text file with one line per serial number (all for the same product)."
337msgstr ""
338
339#. module: product_serial
340#: view:stock.picking.prodlot.selection:0
341msgid "Run From File"
342msgstr ""
343
344#. module: product_serial
345#: view:stock.move:0
189msgid "Manual split"346msgid "Manual split"
190msgstr ""347msgstr ""
191348
@@ -203,4 +360,15 @@
203#: model:ir.actions.act_window,name:product_serial.act_prodlot_location_open360#: model:ir.actions.act_window,name:product_serial.act_prodlot_location_open
204msgid "Prodlots"361msgid "Prodlots"
205msgstr ""362msgstr ""
206 363
364#. module: product_serial
365#: code:addons/product_serial/wizard/prodlot_wizard.py:114
366#, python-format
367msgid "Missing parameters"
368msgstr ""
369
370#. module: product_serial
371#: selection:product.product,lot_split_type:0
372msgid "Single"
373msgstr ""
374
207375
=== modified file 'product_serial/product_demo.xml'
--- product_serial/product_demo.xml 2013-05-11 16:09:05 +0000
+++ product_serial/product_demo.xml 2015-12-04 10:45:32 +0000
@@ -16,6 +16,7 @@
16 <field name="track_production">True</field>16 <field name="track_production">True</field>
17 <field name="track_incoming">True</field>17 <field name="track_incoming">True</field>
18 <field name="track_outgoing">True</field>18 <field name="track_outgoing">True</field>
19 <field name="track_internal">True</field>
19</record>20</record>
2021
21</data>22</data>
2223
=== modified file 'product_serial/stock.py'
--- product_serial/stock.py 2013-11-29 15:25:53 +0000
+++ product_serial/stock.py 2015-12-04 10:45:32 +0000
@@ -46,6 +46,12 @@
46 res[move.id] = move.prodlot_id and move.prodlot_id.name or False46 res[move.id] = move.prodlot_id and move.prodlot_id.name or False
47 return res47 return res
4848
49 def _prepare_prodlot_generation(self, cr, uid, prodlot_name, move, context=None):
50 return {
51 'name': prodlot_name,
52 'product_id': move.product_id.id,
53 }
54
49 def _set_prodlot_code(self, cr, uid, ids, name, value, arg, context=None):55 def _set_prodlot_code(self, cr, uid, ids, name, value, arg, context=None):
50 if not value: return False56 if not value: return False
5157
@@ -53,15 +59,14 @@
53 ids = [ids]59 ids = [ids]
5460
55 for move in self.browse(cr, uid, ids, context=context):61 for move in self.browse(cr, uid, ids, context=context):
56 product_id = move.product_id.id
57 existing_prodlot = move.prodlot_id62 existing_prodlot = move.prodlot_id
58 if existing_prodlot: #avoid creating a prodlot twice63 if existing_prodlot: #avoid creating a prodlot twice
59 self.pool.get('stock.production.lot').write(cr, uid, existing_prodlot.id, {'name': value})64 self.pool.get('stock.production.lot').write(cr, uid, existing_prodlot.id, {'name': value})
60 else:65 else:
61 prodlot_id = self.pool.get('stock.production.lot').create(cr, uid, {66 plot_vals = self._prepare_prodlot_generation(
62 'name': value,67 cr, uid, value, move, context=context)
63 'product_id': product_id,68 prodlot_id = self.pool.get('stock.production.lot').create(
64 })69 cr, uid, plot_vals, context=context)
65 move.write({'prodlot_id' : prodlot_id})70 move.write({'prodlot_id' : prodlot_id})
6671
67 def _get_tracking_code(self, cr, uid, ids, field_name, arg, context=None):72 def _get_tracking_code(self, cr, uid, ids, field_name, arg, context=None):
@@ -194,7 +199,7 @@
194199
195 return result200 return result
196201
197 # Because stock move line can be splitted by the module, we merge202 # Because stock move line can be split by the module, we merge
198 # invoice lines (if option 'is_group_invoice_line' is activated for the company)203 # invoice lines (if option 'is_group_invoice_line' is activated for the company)
199 # at the following conditions :204 # at the following conditions :
200 # - the product is the same and205 # - the product is the same and
201206
=== modified file 'product_serial/stock_view.xml'
--- product_serial/stock_view.xml 2013-10-09 09:54:20 +0000
+++ product_serial/stock_view.xml 2015-12-04 10:45:32 +0000
@@ -32,11 +32,11 @@
32 <field name="product_packaging" groups="product.group_stock_packaging" domain="[('product_id','=',product_id)]" invisible="context.get('picking_type') != 'out' or False"/>32 <field name="product_packaging" groups="product.group_stock_packaging" domain="[('product_id','=',product_id)]" invisible="context.get('picking_type') != 'out' or False"/>
33 <field name="location_id" groups="stock.group_locations"/>33 <field name="location_id" groups="stock.group_locations"/>
34 </xpath>34 </xpath>
35 <xpath expr="//button[@name='%(stock.track_line)d']" position="replace">35 <xpath expr="//button[@name='%(stock.track_line)d']" position="after">
36 <button name="split_move" string="Manual Split"36 <button name="split_move" string="Auto-Split"
37 groups="stock.group_production_lot"37 groups="stock.group_production_lot"
38 states="draft,waiting,confirmed,assigned"38 states="draft,waiting,confirmed,assigned"
39 type="object" icon="gtk-justify-fill" />39 type="object" icon="STOCK_COPY" />
40 </xpath>40 </xpath>
41 <!-- In the form view of Incoming shipments, add the "new_prodlot_code" field -->41 <!-- In the form view of Incoming shipments, add the "new_prodlot_code" field -->
42 <xpath expr="//field[@name='prodlot_id']" position="before">42 <xpath expr="//field[@name='prodlot_id']" position="before">
@@ -89,83 +89,5 @@
89 <field eval="'ir.actions.act_window,%d'%act_prodlot_location_open" name="value"/>89 <field eval="'ir.actions.act_window,%d'%act_prodlot_location_open" name="value"/>
90</record>90</record>
9191
92 <!-- Wizard to help users input production lots in batch -->
93<!-- TODO Nan-TIc : port to v6
94 <record id="view_stock_picking_prodlot_selection" model="ir.ui.view">
95 <field name="name">stock.picking.prodlot.selection</field>
96 <field name="model">stock.picking.prodlot.selection</field>
97 <field name="type">form</field>
98 <field name="arch" type="xml">
99 <form string="Select Production Lots">
100 <field name="product_id" colspan="4"/>
101 <field name="first_lot"/>
102 <field name="last_lot"/>
103 <button type="object" name="action_cancel" string="Cancel" icon="gtk-cancel" special="cancel" colspan="2"/>
104 <button type="object" name="action_accept" string="Accept" icon="gtk-ok" colspan="2"/>
105 </form>
106 </field>
107 </record>
108
109 <record model="ir.actions.act_window" id="action_prodlot_selection">
110 <field name="name">Select Production Lots</field>
111 <field name="res_model">stock.picking.prodlot.selection</field>
112 <field name="view_type">form</field>
113 <field name="view_mode">form</field>
114 <field name="target">new</field>
115 </record>
116
117 <record id="view_picking_form" model="ir.ui.view">
118 <field name="name">stock.picking.form.prodlot.selection</field>
119 <field name="model">stock.picking</field>
120 <field name="type">form</field>
121 <field name="inherit_id" ref="stock.view_picking_form"/>
122 <field name="arch" type="xml">
123 <xpath expr="/form/notebook/page/group/label[@colspan='6']" position="replace">
124 <label colspan="5"/>
125 <button type="action" name="%(action_prodlot_selection)d" string="Spread Production Lots" states="draft,confirmed,assigned"/>
126 </xpath>
127 </field>
128 </record>
129
130 <record id="view_picking_in_form" model="ir.ui.view">
131 <field name="name">stock.picking.in.form.prodlot.selection</field>
132 <field name="model">stock.picking</field>
133 <field name="type">form</field>
134 <field name="inherit_id" ref="stock.view_picking_in_form"/>
135 <field name="arch" type="xml">
136 <xpath expr="/form/notebook/page/group/label[@colspan='5']" position="replace">
137 <label colspan="4"/>
138 <button type="action" name="%(action_prodlot_selection)d" string="Spread Production Lots" states="draft,confirmed,assigned"/>
139 </xpath>
140 </field>
141 </record>
142
143 <record id="view_picking_out_form" model="ir.ui.view">
144 <field name="name">stock.picking.out.form</field>
145 <field name="model">stock.picking</field>
146 <field name="type">form</field>
147 <field name="inherit_id" ref="stock.view_picking_out_form"/>
148 <field name="arch" type="xml">
149 <xpath expr="/form/notebook/page/group/label[@colspan='6']" position="replace">
150 <label colspan="5"/>
151 <button type="action" name="%(action_prodlot_selection)d" string="Spread Production Lots" states="draft,confirmed,assigned"/>
152 </xpath>
153 </field>
154 </record>
155
156 <record id="view_picking_delivery_form" model="ir.ui.view">
157 <field name="name">stock.picking.delivery.form</field>
158 <field name="model">stock.picking</field>
159 <field name="type">form</field>
160 <field name="inherit_id" ref="stock.view_picking_delivery_form"/>
161 <field name="arch" type="xml">
162 <xpath expr="/form/notebook/page/group/label[@colspan='6']" position="replace">
163 <label colspan="5"/>
164 <button type="action" name="%(action_prodlot_selection)d" string="Spread Production Lots" states="draft,confirmed,assigned"/>
165 </xpath>
166 </field>
167 </record>
168-->
169
170</data>92</data>
171</openerp>93</openerp>
17294
=== modified file 'product_serial/wizard/__init__.py'
--- product_serial/wizard/__init__.py 2013-08-01 12:40:27 +0000
+++ product_serial/wizard/__init__.py 2015-12-04 10:45:32 +0000
@@ -20,5 +20,4 @@
20#20#
21##############################################################################21##############################################################################
2222
23import prodlot_wizard23from . import prodlot_wizard
24
2524
=== modified file 'product_serial/wizard/prodlot_wizard.py'
--- product_serial/wizard/prodlot_wizard.py 2013-08-01 12:40:27 +0000
+++ product_serial/wizard/prodlot_wizard.py 2015-12-04 10:45:32 +0000
@@ -24,95 +24,186 @@
2424
25from openerp.osv import orm, fields25from openerp.osv import orm, fields
26from openerp.tools.translate import _26from openerp.tools.translate import _
27from openerp.tools.safe_eval import safe_eval
28import base64
2729
2830
29class stock_picking_prodlot_selection_wizard(orm.TransientModel):31class stock_picking_prodlot_selection_wizard(orm.TransientModel):
30 _name = 'stock.picking.prodlot.selection'32 _name = 'stock.picking.prodlot.selection'
33 _description = "Select or Create Production Lots"
3134
32 _columns = {35 _columns = {
33 'product_id': fields.many2one('product.product', 'Product', required=True),36 'product_id': fields.many2one(
37 'product.product', 'Product', required=True),
34 'prefix': fields.char('Prefix', size=256),38 'prefix': fields.char('Prefix', size=256),
35 'suffix': fields.char('Suffix', size=256),39 'suffix': fields.char('Suffix', size=256),
36 'first_number': fields.char('First Number', size=256, required=True),40 'first_number': fields.char('First Number', size=256),
37 'last_number': fields.char('Last Number', size=256, required=True),41 'last_number': fields.char('Last Number', size=256),
42 'prodlot_file': fields.binary(
43 'Serial Numbers File',
44 help="The serial numbers file should be a text file with one line "
45 "per serial number (all for the same product)."),
38 'create_prodlots': fields.boolean('Create New Serial Numbers'),46 'create_prodlots': fields.boolean('Create New Serial Numbers'),
39 }47 }
4048
49 def _default_create_prodlots(self, cr, uid, context=None):
50 if context is None:
51 context = {}
52 return context.get('default_create_prodlots')
53
41 _defaults = {54 _defaults = {
42 'create_prodlots': False,55 'create_prodlots': _default_create_prodlots,
43 }56 }
4457
4558 def _get_company_and_stock_moves(self, cr, uid, context=None):
46 def select_or_create_prodlots(self, cr, uid, ids, context=None):59 '''This function is designed to be generic, in order to work
60 on the objects stock.picking (product_serial module) and
61 mrp.production (module product_serial_mrp)'''
47 if context is None:62 if context is None:
48 context = {}63 context = {}
49 if not ids:64 assert ('active_id' in context), "Missing 'active_id' key in context"
50 return {}65 assert ('active_model' in context), \
51 if not 'active_id' in context:66 "Missing 'active_model' key in context"
52 return {}67 assert ('stock_move_field' in context), \
5368 "Missing 'stock_move_field' key in context"
54 prodlot_obj = self.pool['stock.production.lot']69
70 active_id = context['active_id']
71 assert isinstance(active_id, int), \
72 "Wrong value for 'active_id' in context"
73 parent_obj = self.pool[context['active_model']]
74 parent_rec = parent_obj.browse(cr, uid, active_id, context=context)
75 company_id = parent_rec.company_id.id
76 stock_moves = safe_eval(
77 'parent.%s' % context['stock_move_field'],
78 {'parent': parent_rec})
79 return company_id, stock_moves
80
81 def select_or_create_prodlots_from_file(
82 self, cr, uid, ids, context=None):
83 assert len(ids) == 1, "Only one ID"
84 if not ids:
85 return {}
86
87 record = self.browse(cr, uid, ids[0], context)
88 if not record.prodlot_file:
89 raise orm.except_orm(
90 _('Error'),
91 _("You should upload a text file containing the serial "
92 "numbers."))
93 full_prodlot_str = base64.decodestring(record.prodlot_file)
94 full_prodlot_seq = full_prodlot_str.splitlines()
95 # Remove empty lines
96 prodlot_seq = [prodlot for prodlot in full_prodlot_seq if prodlot]
97 company_id, stock_moves = self._get_company_and_stock_moves(
98 cr, uid, context=context)
99 return self._select_or_create_prodlots(
100 cr, uid, company_id, stock_moves, record.product_id, prodlot_seq,
101 record.create_prodlots, context=context)
102
103 def select_or_create_prodlots_from_interval(
104 self, cr, uid, ids, context=None):
105 assert len(ids) == 1, "Only one ID"
106 if not ids:
107 return {}
108
55 record = self.browse(cr, uid, ids[0], context)109 record = self.browse(cr, uid, ids[0], context)
56 prefix = record.prefix or ''110 prefix = record.prefix or ''
57 suffix = record.suffix or ''111 suffix = record.suffix or ''
112 if not record.first_number or not record.last_number:
113 raise orm.except_orm(
114 _('Missing parameters'),
115 _("You should enter a value for the First Number and the "
116 "Last Number"))
58 try:117 try:
59 first_number = int(record.first_number)118 first_number = int(record.first_number)
60 except:119 except:
61 raise orm.except_orm(_('Invalid First Number'), _("The field 'First Number' should only contain digits."))120 raise orm.except_orm(
121 _('Invalid First Number'),
122 _("The field 'First Number' should only contain digits."))
62123
63 try:124 try:
64 last_number = int(record.last_number)125 last_number = int(record.last_number)
65 except:126 except:
66 raise orm.except_orm(_('Invalid Last Number'), _("The field 'Last Number' should only contain digits."))127 raise orm.except_orm(
128 _('Invalid Last Number'),
129 _("The field 'Last Number' should only contain digits."))
130
131 if int(record.first_number) <= 0 or int(record.last_number) <= 0:
132 raise orm.except_orm(
133 _('Invalid Numbers'),
134 _("The First and Last Numbers should be strictly positive."))
67135
68 if last_number < first_number:136 if last_number < first_number:
69 raise orm.except_orm(_('Invalid Numbers'), _('The First Number must be lower than the Last Number.'))137 raise orm.except_orm(
138 _('Invalid Numbers'),
139 _('The First Number must be lower than the Last Number.'))
70140
71 if len(record.first_number) != len(record.last_number):141 if len(record.first_number) != len(record.last_number):
72 raise orm.except_orm(_('Invalid Lot Numbers'), _('First and Last Numbers must have the same length.'))142 raise orm.except_orm(
143 _('Invalid Lot Numbers'),
144 _('First and Last Numbers must have the same length.'))
73145
74 number_length = len(record.first_number)146 number_length = len(record.first_number)
147 prodlot_seq = [
148 '%s%0*d%s' % (prefix, number_length, current_number, suffix)
149 for current_number in range(first_number, last_number+1)
150 ]
151 company_id, stock_moves = self._get_company_and_stock_moves(
152 cr, uid, context=context)
153 return self._select_or_create_prodlots(
154 cr, uid, company_id, stock_moves, record.product_id, prodlot_seq,
155 record.create_prodlots, context=context)
75156
76 picking_id = context['active_id']157 def _select_or_create_prodlots(
77 current_number = first_number158 self, cr, uid, company_id, stock_moves, product, prodlot_seq,
78 picking = self.pool['stock.picking'].browse(cr, uid, picking_id, context=context)159 create_prodlots, context=None):
79 company_id = picking.company_id.id160 assert prodlot_seq and isinstance(prodlot_seq, list)
80 for move in picking.move_lines:161 prodlot_obj = self.pool['stock.production.lot']
81 if move.prodlot_id or move.product_id != record.product_id:162 for move in stock_moves:
163 if move.prodlot_id or move.product_id != product:
82 continue164 continue
83165 try:
84 current_lot = '%%s%%0%dd%%s' % number_length % (prefix, current_number, suffix)166 current_lot = prodlot_seq.pop(0)
85 if record.create_prodlots:167 except:
168 break
169 if create_prodlots:
86 # Create new prodlot170 # Create new prodlot
87 lot_id_on_move = prodlot_obj.create(cr, uid, {171 lot_id_on_move = prodlot_obj.create(
88 'product_id': record.product_id.id,172 cr, uid, {
89 'name': current_lot,173 'product_id': product.id,
90 'company_id': company_id,174 'name': current_lot,
91 }, context=context)175 'company_id': company_id,
176 }, context=context)
92 else:177 else:
93 # Search existing prodlots178 # Search existing prodlots
94 lot_ids = prodlot_obj.search(cr, uid, [('name','=',current_lot)], limit=1, context=context)179 lot_ids = prodlot_obj.search(
180 cr, uid, [('name', '=', current_lot)], limit=1,
181 context=context)
95 if not lot_ids:182 if not lot_ids:
96 raise orm.except_orm(_('Invalid lot numbers'), _('Production lot %s not found.') % current_lot)183 raise orm.except_orm(
184 _('Invalid Serial Number'),
185 _('Serial Number %s not found.') % current_lot)
97186
98 ctx = context.copy()187 ctx = context.copy()
99 ctx['location_id'] = move.location_id.id188 ctx['location_id'] = move.location_id.id
100 prodlot = self.pool.get('stock.production.lot').browse(cr, uid, lot_ids[0], ctx)189 prodlot = self.pool.get('stock.production.lot').browse(
190 cr, uid, lot_ids[0], ctx)
101191
102 if prodlot.product_id != record.product_id:192 if prodlot.product_id != product:
103 raise orm.except_orm(_('Invalid lot numbers'), _('Production lot %s exists but not for product %s.') % (current_lot, record.product_id.name))193 raise orm.except_orm(
194 _('Invalid Serial Number'),
195 _('Serial Number %s exists but not for product %s.')
196 % (current_lot, product.name))
104197
105 if prodlot.stock_available < move.product_qty:198 if prodlot.stock_available < move.product_qty:
106 raise orm.except_orm(_('Invalid lot numbers'), _('Not enough stock available of production lot %s.') % current_lot)199 raise orm.except_orm(
200 _('Invalid Serial Number'),
201 _('Not enough stock available of serial number %s.')
202 % current_lot)
107 lot_id_on_move = lot_ids[0]203 lot_id_on_move = lot_ids[0]
108204
109 self.pool.get('stock.move').write(cr, uid, [move.id], {205 self.pool.get('stock.move').write(
110 'prodlot_id': lot_id_on_move,206 cr, uid, [move.id], {'prodlot_id': lot_id_on_move},
111 }, context=context)207 context=context)
112
113 current_number += 1
114 if current_number > last_number:
115 break
116208
117 return True209 return True
118
119210
=== modified file 'product_serial/wizard/prodlot_wizard_view.xml'
--- product_serial/wizard/prodlot_wizard_view.xml 2013-08-01 12:40:27 +0000
+++ product_serial/wizard/prodlot_wizard_view.xml 2015-12-04 10:45:32 +0000
@@ -7,17 +7,23 @@
7 <field name="name">stock.picking.prodlot.selection</field>7 <field name="name">stock.picking.prodlot.selection</field>
8 <field name="model">stock.picking.prodlot.selection</field>8 <field name="model">stock.picking.prodlot.selection</field>
9 <field name="arch" type="xml">9 <field name="arch" type="xml">
10 <form string="Select Production Lots" version="7.0">10 <form string="Select or Create Production Lots" version="7.0">
11 <group>11 <group name="common">
12 <field name="product_id" />12 <field name="product_id" />
13 <field name="create_prodlots"/>
14 </group>
15 <group name="from_interval" string="From Interval">
13 <field name="prefix"/>16 <field name="prefix"/>
14 <field name="first_number"/>17 <field name="first_number"/>
15 <field name="last_number"/>18 <field name="last_number"/>
16 <field name="suffix"/>19 <field name="suffix"/>
17 <field name="create_prodlots"/>20 <button type="object" name="select_or_create_prodlots_from_interval" string="Run From Interval" class="oe_highlight"/>
21 </group>
22 <group name="from_file" string="From File">
23 <field name="prodlot_file"/>
24 <button type="object" name="select_or_create_prodlots_from_file" string="Run From File" class="oe_highlight"/>
18 </group>25 </group>
19 <footer>26 <footer>
20 <button type="object" name="select_or_create_prodlots" string="Validate" class="oe_highlight"/>
21 <button string="Cancel" special="cancel" class="oe_link"/>27 <button string="Cancel" special="cancel" class="oe_link"/>
22 </footer>28 </footer>
23 </form>29 </form>
@@ -26,7 +32,7 @@
2632
2733
28<record id="action_prodlot_selection" model="ir.actions.act_window">34<record id="action_prodlot_selection" model="ir.actions.act_window">
29 <field name="name">Select Production Lots</field>35 <field name="name">Select or Create Serial Numbers</field>
30 <field name="res_model">stock.picking.prodlot.selection</field>36 <field name="res_model">stock.picking.prodlot.selection</field>
31 <field name="view_type">form</field>37 <field name="view_type">form</field>
32 <field name="view_mode">form</field>38 <field name="view_mode">form</field>
@@ -41,7 +47,7 @@
41 <field name="arch" type="xml">47 <field name="arch" type="xml">
42 <xpath expr="//field[@name='origin']/.." position="after">48 <xpath expr="//field[@name='origin']/.." position="after">
43 <group name="prodlot_wizard">49 <group name="prodlot_wizard">
44 <button type="action" name="%(action_prodlot_selection)d" string="Spread Production Lots" states="draft,confirmed,assigned"/>50 <button type="action" name="%(action_prodlot_selection)d" string="Spread Serial Numbers" states="draft,confirmed,assigned" context="{'stock_move_field': 'move_lines'}"/>
45 </group>51 </group>
46 </xpath>52 </xpath>
47 </field>53 </field>
@@ -55,7 +61,7 @@
55 <field name="arch" type="xml">61 <field name="arch" type="xml">
56 <xpath expr="//field[@name='origin']/.." position="after">62 <xpath expr="//field[@name='origin']/.." position="after">
57 <group name="prodlot_wizard">63 <group name="prodlot_wizard">
58 <button type="action" name="%(action_prodlot_selection)d" string="Spread Production Lots" states="draft,confirmed,assigned"/>64 <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}"/>
59 </group>65 </group>
60 </xpath>66 </xpath>
61 </field>67 </field>
@@ -69,7 +75,7 @@
69 <field name="arch" type="xml">75 <field name="arch" type="xml">
70 <xpath expr="//field[@name='origin']/.." position="after">76 <xpath expr="//field[@name='origin']/.." position="after">
71 <group name="prodlot_wizard">77 <group name="prodlot_wizard">
72 <button type="action" name="%(action_prodlot_selection)d" string="Spread Production Lots" states="draft,confirmed,assigned"/>78 <button type="action" name="%(action_prodlot_selection)d" string="Spread Serial Numbers" states="draft,confirmed,assigned" context="{'stock_move_field': 'move_lines'}"/>
73 </group>79 </group>
74 </xpath>80 </xpath>
75 </field>81 </field>
7682
=== added directory 'product_serial_purchase'
=== added file 'product_serial_purchase/__init__.py'
--- product_serial_purchase/__init__.py 1970-01-01 00:00:00 +0000
+++ product_serial_purchase/__init__.py 2015-12-04 10:45:32 +0000
@@ -0,0 +1,23 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# Product Serial Purchase module for OpenERP
5# Copyright (C) 2013-2014 Akretion (http://www.akretion.com)
6# @author Alexis de Lattre <alexis.delattre@akretion.com>
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as
10# published by the Free Software Foundation, either version 3 of the
11# License, or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21##############################################################################
22
23from . import purchase
024
=== added file 'product_serial_purchase/__openerp__.py'
--- product_serial_purchase/__openerp__.py 1970-01-01 00:00:00 +0000
+++ product_serial_purchase/__openerp__.py 2015-12-04 10:45:32 +0000
@@ -0,0 +1,44 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# Product Serial Purchase module for OpenERP
5# Copyright (C) 2013-2014 Akretion (http://www.akretion.com)
6# @author Alexis de Lattre <alexis.delattre@akretion.com>
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as
10# published by the Free Software Foundation, either version 3 of the
11# License, or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21##############################################################################
22
23
24{
25 'name': 'Product Serial Purchase',
26 'version': '0.1',
27 'category': 'Inventory, Logistic, Storage',
28 'license': 'AGPL-3',
29 'description': """
30Product Serial Purchase
31=======================
32
33The 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.
34
35Please contact Alexis de Lattre from Akretion <alexis.delattre@akretion.com> for any help or question about this module.
36 """,
37 'author': 'Akretion',
38 'website': 'http://www.akretion.com',
39 'depends': ['purchase', 'product_serial'],
40 'data': [],
41 'images': [],
42 'installable': True,
43 'active': False,
44}
045
=== added directory 'product_serial_purchase/i18n'
=== added file 'product_serial_purchase/i18n/product_serial_purchase.pot'
--- product_serial_purchase/i18n/product_serial_purchase.pot 1970-01-01 00:00:00 +0000
+++ product_serial_purchase/i18n/product_serial_purchase.pot 2015-12-04 10:45:32 +0000
@@ -0,0 +1,34 @@
1# Translation of OpenERP Server.
2# This file contains the translation of the following modules:
3# * product_serial_purchase
4#
5msgid ""
6msgstr ""
7"Project-Id-Version: OpenERP Server 7.0\n"
8"Report-Msgid-Bugs-To: \n"
9"POT-Creation-Date: 2014-01-03 18:27+0000\n"
10"PO-Revision-Date: 2014-01-03 18:27+0000\n"
11"Last-Translator: <>\n"
12"Language-Team: \n"
13"MIME-Version: 1.0\n"
14"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Transfer-Encoding: \n"
16"Plural-Forms: \n"
17
18#. module: product_serial_purchase
19#: code:addons/product_serial_purchase/purchase.py:101
20#, python-format
21msgid "Product '%s' has 'Lot split type' = 'Logistical Unit' but it has no packaging information."
22msgstr ""
23
24#. module: product_serial_purchase
25#: code:addons/product_serial_purchase/purchase.py:100
26#, python-format
27msgid "Error:"
28msgstr ""
29
30#. module: product_serial_purchase
31#: model:ir.model,name:product_serial_purchase.model_purchase_order
32msgid "Purchase Order"
33msgstr ""
34
035
=== added file 'product_serial_purchase/purchase.py'
--- product_serial_purchase/purchase.py 1970-01-01 00:00:00 +0000
+++ product_serial_purchase/purchase.py 2015-12-04 10:45:32 +0000
@@ -0,0 +1,131 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# Product Serial Purchase module for OpenERP
5# Copyright (C) 2013-2014 Akretion (http://www.akretion.com)
6# Copyright (C) OpenERP S.A.
7# @author Alexis de Lattre <alexis.delattre@akretion.com>
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU Affero General Public License as
11# published by the Free Software Foundation, either version 3 of the
12# License, or (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU Affero General Public License for more details.
18#
19# You should have received a copy of the GNU Affero General Public License
20# along with this program. If not, see <http://www.gnu.org/licenses/>.
21#
22##############################################################################
23
24from openerp.osv import orm
25from openerp.tools.translate import _
26from openerp import netsvc
27
28
29class purchase_order(orm.Model):
30 _inherit = 'purchase.order'
31
32 def _prepare_order_line_move(
33 self, cr, uid, order, order_line, picking_id, context=None):
34 if context is None:
35 context = {}
36 vals = super(purchase_order, self)._prepare_order_line_move(
37 cr, uid, order, order_line, picking_id, context=context)
38 if context.get('force_qty'):
39 vals['product_qty'] = context.get('force_qty')
40 vals['product_uos_qty'] = context.get('force_qty')
41 if context.get('force_move_dest_id'):
42 vals['move_dest_id'] = context.get('force_move_dest_id')
43 return vals
44
45 def _add_stock_move_on_picking(
46 self, cr, uid, order, line, picking_id, qty, proc_move_dest_id,
47 todo_moves, context=None):
48 if context is None:
49 context = {}
50 move_ctx = context.copy()
51 move_ctx['force_qty'] = qty
52 for move_dest_id, proc_qty in proc_move_dest_id.items():
53 if proc_qty == qty:
54 move_ctx['force_move_dest_id'] = move_dest_id
55 proc_move_dest_id.pop(move_dest_id)
56 self.pool['stock.move'].write(
57 cr, uid, move_dest_id, {
58 'location_id': order.location_id.id},
59 context=context)
60 break
61 move_id = self.pool['stock.move'].create(
62 cr, uid, self._prepare_order_line_move(
63 cr, uid, order, line, picking_id,
64 context=move_ctx))
65 todo_moves.append(move_id)
66 return move_id
67
68 def _create_pickings(
69 self, cr, uid, order, order_lines, picking_id=False, context=None):
70 if not picking_id:
71 picking_id = self.pool.get('stock.picking').create(
72 cr, uid, self._prepare_order_picking(
73 cr, uid, order, context=context))
74 todo_moves = []
75 stock_move = self.pool['stock.move']
76 wf_service = netsvc.LocalService("workflow")
77 for line in order_lines:
78 if not line.product_id:
79 continue
80 if line.product_id.type in ('product', 'consu'):
81 # We neutralise the procurement stock move now, to be sure
82 # that we don't miss this important task (for exemple when
83 # the user changes the quantity in the PO)
84 if (
85 line.move_dest_id
86 and line.move_dest_id.state != 'done'):
87 line.move_dest_id.write(
88 {'location_id': order.location_id.id}, context=context)
89
90 proc_ids = self.pool['procurement.order'].search(
91 cr, uid, [
92 ('purchase_line_id', '=', line.id),
93 ('product_id', '=', line.product_id.id),
94 ('state', '!=', 'cancel'),
95 ], context=context)
96 proc_move_dest_id = {}
97 # key = move_dest_id ; value = product_qty
98 for procurement in self.pool['procurement.order'].browse(
99 cr, uid, proc_ids, context=context):
100 proc_move_dest_id[procurement.move_id.id] = \
101 procurement.product_qty
102
103 qty = line.product_qty
104 if line.product_id.lot_split_type == 'lu':
105 if not line.product_id.packaging:
106 raise orm.except_orm(
107 _('Error:'),
108 _("Product '%s' has 'Lot split type' = "
109 "'Logistical Unit' but it has no packaging "
110 "information.")
111 % (line.product_id.name))
112 lu_qty = line.product_id.packaging[0].qty
113 elif line.product_id.lot_split_type == 'single':
114 lu_qty = 1
115 else:
116 lu_qty = qty
117 while qty >= lu_qty:
118 self._add_stock_move_on_picking(
119 cr, uid, order, line, picking_id, lu_qty,
120 proc_move_dest_id, todo_moves, context=context)
121 qty -= lu_qty
122 if qty > 0:
123 # Create last move/proc for the remaining qty
124 self._add_stock_move_on_picking(
125 cr, uid, order, line, picking_id, qty,
126 proc_move_dest_id, todo_moves, context=context)
127 stock_move.action_confirm(cr, uid, todo_moves)
128 stock_move.force_assign(cr, uid, todo_moves)
129 wf_service.trg_validate(
130 uid, 'stock.picking', picking_id, 'button_confirm', cr)
131 return [picking_id]
0132
=== added directory 'product_serial_sale_stock'
=== added file 'product_serial_sale_stock/__init__.py'
--- product_serial_sale_stock/__init__.py 1970-01-01 00:00:00 +0000
+++ product_serial_sale_stock/__init__.py 2015-12-04 10:45:32 +0000
@@ -0,0 +1,23 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# Product Serial Sale Stock module for OpenERP
5# Copyright (C) 2013 Akretion (http://www.akretion.com)
6# @author Alexis de Lattre <alexis.delattre@akretion.com>
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as
10# published by the Free Software Foundation, either version 3 of the
11# License, or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21##############################################################################
22
23from . import sale_stock
024
=== added file 'product_serial_sale_stock/__openerp__.py'
--- product_serial_sale_stock/__openerp__.py 1970-01-01 00:00:00 +0000
+++ product_serial_sale_stock/__openerp__.py 2015-12-04 10:45:32 +0000
@@ -0,0 +1,46 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# Product Serial Sale Stock module for OpenERP
5# Copyright (C) 2013 Akretion (http://www.akretion.com)
6# @author Alexis de Lattre <alexis.delattre@akretion.com>
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as
10# published by the Free Software Foundation, either version 3 of the
11# License, or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21##############################################################################
22
23
24{
25 'name': 'Product Serial Sale Stock',
26 'version': '0.1',
27 'category': 'Inventory, Logistic, Storage',
28 'license': 'AGPL-3',
29 'description': """
30Product Serial Sale Stock
31=========================
32
33The 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.
34
35This module uses the monkey-patching method to replace the code of the method _create_pickings_and_procurements() by a different code.
36
37Please contact Alexis de Lattre from Akretion <alexis.delattre@akretion.com> for any help or question about this module.
38 """,
39 'author': 'Akretion',
40 'website': 'http://www.akretion.com',
41 'depends': ['product_serial', 'sale_stock'],
42 'data': [],
43 'images': [],
44 'installable': True,
45 'active': False,
46}
047
=== added directory 'product_serial_sale_stock/i18n'
=== added file 'product_serial_sale_stock/i18n/product_serial_sale_stock.pot'
--- product_serial_sale_stock/i18n/product_serial_sale_stock.pot 1970-01-01 00:00:00 +0000
+++ product_serial_sale_stock/i18n/product_serial_sale_stock.pot 2015-12-04 10:45:32 +0000
@@ -0,0 +1,34 @@
1# Translation of OpenERP Server.
2# This file contains the translation of the following modules:
3# * product_serial_sale_stock
4#
5msgid ""
6msgstr ""
7"Project-Id-Version: OpenERP Server 7.0\n"
8"Report-Msgid-Bugs-To: \n"
9"POT-Creation-Date: 2014-01-03 18:28+0000\n"
10"PO-Revision-Date: 2014-01-03 18:28+0000\n"
11"Last-Translator: <>\n"
12"Language-Team: \n"
13"MIME-Version: 1.0\n"
14"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Transfer-Encoding: \n"
16"Plural-Forms: \n"
17
18#. module: product_serial_sale_stock
19#: code:addons/product_serial_sale_stock/sale_stock.py:100
20#, python-format
21msgid "Product '%s' has 'Lot split type' = 'Logistical Unit' but it has no packaging information."
22msgstr ""
23
24#. module: product_serial_sale_stock
25#: code:addons/product_serial_sale_stock/sale_stock.py:99
26#, python-format
27msgid "Error:"
28msgstr ""
29
30#. module: product_serial_sale_stock
31#: model:ir.model,name:product_serial_sale_stock.model_sale_order
32msgid "Sales Order"
33msgstr ""
34
035
=== added file 'product_serial_sale_stock/sale_stock.py'
--- product_serial_sale_stock/sale_stock.py 1970-01-01 00:00:00 +0000
+++ product_serial_sale_stock/sale_stock.py 2015-12-04 10:45:32 +0000
@@ -0,0 +1,173 @@
1# -*- encoding: utf-8 -*-
2##############################################################################
3#
4# Product Serial Sale Stock module for OpenERP
5# Copyright (C) 2013-2014 Akretion (http://www.akretion.com)
6# Copyright (C) OpenERP S.A.
7# @author Alexis de Lattre <alexis.delattre@akretion.com>
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU Affero General Public License as
11# published by the Free Software Foundation, either version 3 of the
12# License, or (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU Affero General Public License for more details.
18#
19# You should have received a copy of the GNU Affero General Public License
20# along with this program. If not, see <http://www.gnu.org/licenses/>.
21#
22##############################################################################
23
24from openerp.osv import orm
25from openerp.tools.translate import _
26from openerp import netsvc
27# For Monkey-patching:
28from openerp.addons.sale_stock import sale_stock
29
30
31class sale_order(orm.Model):
32 _inherit = 'sale.order'
33
34 def _prepare_order_line_procurement(
35 self, cr, uid, order, line, move_id, date_planned, context=None):
36 if context is None:
37 context = {}
38 vals = super(sale_order, self)._prepare_order_line_procurement(
39 cr, uid, order, line, move_id, date_planned, context=context)
40 if context.get('force_qty'):
41 vals['product_qty'] = context.get('force_qty')
42 vals['product_uos_qty'] = context.get('force_qty')
43 return vals
44
45 def _prepare_order_line_move(
46 self, cr, uid, order, line, picking_id, date_planned,
47 context=None):
48 if context is None:
49 context = {}
50 vals = super(sale_order, self)._prepare_order_line_move(
51 cr, uid, order, line, picking_id, date_planned, context=context)
52 if context.get('force_qty'):
53 vals['product_qty'] = context.get('force_qty')
54 vals['product_uos_qty'] = context.get('force_qty')
55 return vals
56
57 def _create_move_line_and_procurement(
58 self, cr, uid, order, line, picking_id, qty, proc_ids,
59 date_planned, context=None):
60 if context is None:
61 context = {}
62 qty_ctx = context.copy()
63 qty_ctx['force_qty'] = qty
64 picking_obj = self.pool.get('stock.picking')
65 move_obj = self.pool.get('stock.move')
66 procurement_obj = self.pool.get('procurement.order')
67 if line.product_id.type in ('product', 'consu'):
68 if not picking_id:
69 picking_id = picking_obj.create(
70 cr, uid, self._prepare_order_picking(
71 cr, uid, order, context=context),
72 context=context)
73 move_id = move_obj.create(
74 cr, uid, self._prepare_order_line_move(
75 cr, uid, order, line, picking_id, date_planned,
76 context=qty_ctx),
77 context=context)
78 else:
79 # a service has no stock move
80 move_id = False
81
82 proc_id = procurement_obj.create(
83 cr, uid, self._prepare_order_line_procurement(
84 cr, uid, order, line, move_id, date_planned,
85 context=qty_ctx),
86 context=context)
87 proc_ids.append(proc_id)
88 return picking_id
89
90
91class product_serial_sale_stock_installed(orm.AbstractModel):
92 _name = "product.serial.sale.stock.installed"
93 # This class is used only to test if the module is installed or not
94
95
96original_create_pickings_and_procurements = \
97 sale_stock.sale_order._create_pickings_and_procurements
98
99
100def _create_pickings_and_procurements(
101 self, cr, uid, order, order_lines, picking_id=False, context=None):
102 if self.pool.get('product.serial.sale.stock.installed'):
103 # Here is the code that is executed when the module
104 # product_serial_sale_stock is installed
105 if context is None:
106 context = {}
107 proc_ids = []
108
109 for line in order_lines:
110 if line.state == 'done':
111 continue
112
113 date_planned = self._get_date_planned(
114 cr, uid, order, line, order.date_order, context=context)
115
116 if line.product_id and line.product_uom_qty:
117 qty = line.product_uom_qty
118 if line.product_id.lot_split_type == 'lu':
119 if not line.product_id.packaging:
120 raise orm.except_orm(
121 _('Error:'),
122 _("Product '%s' has 'Lot split type' = "
123 "'Logistical Unit' but it has no packaging "
124 "information.")
125 % (line.product_id.name))
126 lu_qty = line.product_id.packaging[0].qty
127 elif line.product_id.lot_split_type == 'single':
128 lu_qty = 1
129 else:
130 lu_qty = qty
131 while qty >= lu_qty:
132 picking_id = self._create_move_line_and_procurement(
133 cr, uid, order, line, picking_id, lu_qty, proc_ids,
134 date_planned, context=context)
135 qty -= lu_qty
136 if qty > 0:
137 # Create last move/proc for the remaining qty
138 picking_id = self._create_move_line_and_procurement(
139 cr, uid, order, line, picking_id, qty, proc_ids,
140 date_planned, context=context)
141 line.write({'procurement_id': proc_ids[-1]})
142 #self.ship_recreate(cr, uid, order, line, move_id, proc_id)
143
144 wf_service = netsvc.LocalService("workflow")
145 if picking_id:
146 wf_service.trg_validate(
147 uid, 'stock.picking', picking_id, 'button_confirm', cr)
148 for proc_id in proc_ids:
149 wf_service.trg_validate(
150 uid, 'procurement.order', proc_id, 'button_confirm', cr)
151
152 val = {}
153 if order.state == 'shipping_except':
154 val['state'] = 'progress'
155 val['shipped'] = False
156
157 if order.order_policy == 'manual':
158 for line in order.order_line:
159 if not line.invoiced and \
160 line.state not in ('cancel', 'draft'):
161 val['state'] = 'manual'
162 break
163 order.write(val)
164 return True
165 else:
166 # This is the code that is executed when the module
167 # product_serial_sale_stock is NOT installed
168 return original_create_pickings_and_procurements(
169 self, cr, uid, order, order_lines, picking_id=picking_id,
170 context=context)
171
172sale_stock.sale_order._create_pickings_and_procurements = \
173 _create_pickings_and_procurements

Subscribers

People subscribed via source and target branches