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