Merge lp:~elbati/sale-wkfl/adding_sale_delivery_term_7 into lp:~sale-core-editors/sale-wkfl/7.0
- adding_sale_delivery_term_7
- Merge into 7.0
Status: | Merged |
---|---|
Merged at revision: | 10 |
Proposed branch: | lp:~elbati/sale-wkfl/adding_sale_delivery_term_7 |
Merge into: | lp:~sale-core-editors/sale-wkfl/7.0 |
Diff against target: |
702 lines (+621/-0) 15 files modified
sale_delivery_term/AUTHORS.txt (+1/-0) sale_delivery_term/__init__.py (+21/-0) sale_delivery_term/__openerp__.py (+44/-0) sale_delivery_term/sale.py (+234/-0) sale_delivery_term/sale_demo.xml (+11/-0) sale_delivery_term/sale_view.xml (+83/-0) sale_delivery_term/security/ir.model.access.csv (+10/-0) sale_delivery_term/test/sale_order_demo.yml (+50/-0) sale_multi_picking/AUTHORS.txt (+1/-0) sale_multi_picking/__init__.py (+21/-0) sale_multi_picking/__openerp__.py (+42/-0) sale_multi_picking/sale.py (+63/-0) sale_multi_picking/sale_demo.xml (+13/-0) sale_multi_picking/sale_view.xml (+23/-0) sale_multi_picking/security/ir.model.access.csv (+4/-0) |
To merge this branch: | bzr merge lp:~elbati/sale-wkfl/adding_sale_delivery_term_7 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Guewen Baconnier @ Camptocamp | code review, no test | Approve | |
Alexandre Fayolle - camptocamp | code review, no test | Needs Fixing | |
Review via email: mp+147611@code.launchpad.net |
Commit message
Description of the change
sale_multi_picking
------------------
This module allows to generate several pickings from the same sale order.
You just have to indicate which order lines have to be grouped in the same picking. When confirming the order, for each group a picking is generated.
sale_delivery_term
------------------
Delivery term for sale orders.
You can configure delivery terms specifying the quantity percentage and the delay for every term line.
You can then associate the term to the 'main' order line and generate the 'detailed' order lines which in turn will generate several pickings according to delivery term (thanks to 'sale_multi_
Alexandre Fayolle - camptocamp (alexandre-fayolle-c2c) wrote : | # |
- 11. By Lorenzo Battistini
-
[FIX] __openerp__.py
Lorenzo Battistini (elbati) wrote : | # |
On 02/14/2013 10:32 AM, Alexandre Fayolle - camptocamp wrote:
> Review: Needs Fixing code review, no test
>
> When porting a module to V7, please:
>
> * update __openerp__.py to use 'data' instead of 'init_xml' and 'update_xml', and 'demo' instead of 'demo_xml'
> * update you python code to inherit from orm.Model (resp orm.TransientModel) rather than osv.osv (resp. osv.osv_memory)
> * update you python code tonot instantiate the model class
Hello Alexandre, thanks for suggestions.
I can't find any standard addon that uses orm.Model, rather osv.Model.
Did you mean osv.Model?
Alexandre Fayolle - camptocamp (alexandre-fayolle-c2c) wrote : | # |
On sam. 16 févr. 2013 19:13:19 CET, Lorenzo Battistini - Agile BG -
Domsense wrote:
> On 02/14/2013 10:32 AM, Alexandre Fayolle - camptocamp wrote:
>> Review: Needs Fixing code review, no test
>>
>> When porting a module to V7, please:
>>
>> * update __openerp__.py to use 'data' instead of 'init_xml' and 'update_xml', and 'demo' instead of 'demo_xml'
>> * update you python code to inherit from orm.Model (resp orm.TransientModel) rather than osv.osv (resp. osv.osv_memory)
>> * update you python code tonot instantiate the model class
>
> Hello Alexandre, thanks for suggestions.
> I can't find any standard addon that uses orm.Model, rather osv.Model.
> Did you mean osv.Model?
>
I mean orm.Model, because the Model class is defined in osv/orm.py and
only import from there in osv/osv.py. See
https:/
for the long version.
Now if you really prefer osv.Model, I won't argue.
--
Alexandre Fayolle
Chef de Projet
Tel : + 33 (0)4 79 26 57 94
Camptocamp France SAS
Savoie Technolac, BP 352
73377 Le Bourget du Lac Cedex
http://
- 12. By Lorenzo Battistini
-
[FIX] classes
- 13. By Lorenzo Battistini
-
[FIX] orm.Model and lines length
- 14. By Lorenzo Battistini
-
[IMP] from . import
- 15. By Lorenzo Battistini
-
[FIX] %d
- 16. By Lorenzo Battistini
-
[IMP] lines_by_group
- 17. By Lorenzo Battistini
-
[imp] ._create_
pickings_ and_procurement s
Lorenzo Battistini (elbati) wrote : | # |
Made the changes.
Thanks
- 18. By Lorenzo Battistini
-
[IMP] enumerate
- 19. By Lorenzo Battistini
-
[FIX] string delimiter
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) wrote : | # |
LGTM
- 20. By Lorenzo Battistini
-
[ADD] first yaml test and relative fixes
- 21. By Lorenzo Battistini
-
[add] testing generate_
detailed_ lines of master line - 22. By Lorenzo Battistini
-
[add] 'I confirm the Quotation' test
Preview Diff
1 | === added directory 'sale_delivery_term' |
2 | === added file 'sale_delivery_term/AUTHORS.txt' |
3 | --- sale_delivery_term/AUTHORS.txt 1970-01-01 00:00:00 +0000 |
4 | +++ sale_delivery_term/AUTHORS.txt 2013-03-15 09:34:21 +0000 |
5 | @@ -0,0 +1,1 @@ |
6 | +Lorenzo Battistini <lorenzo.battistini@agilebg.com> |
7 | |
8 | === added file 'sale_delivery_term/__init__.py' |
9 | --- sale_delivery_term/__init__.py 1970-01-01 00:00:00 +0000 |
10 | +++ sale_delivery_term/__init__.py 2013-03-15 09:34:21 +0000 |
11 | @@ -0,0 +1,21 @@ |
12 | +# -*- coding: utf-8 -*- |
13 | +############################################################################## |
14 | +# |
15 | +# Copyright (C) 2012 Agile Business Group sagl (<http://www.agilebg.com>) |
16 | +# Copyright (C) 2012 Domsense srl (<http://www.domsense.com>) |
17 | +# |
18 | +# This program is free software: you can redistribute it and/or modify |
19 | +# it under the terms of the GNU Affero General Public License as published |
20 | +# by the Free Software Foundation, either version 3 of the License, or |
21 | +# (at your option) any later version. |
22 | +# |
23 | +# This program is distributed in the hope that it will be useful, |
24 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
25 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
26 | +# GNU General Public License for more details. |
27 | +# |
28 | +# You should have received a copy of the GNU Affero General Public License |
29 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
30 | +# |
31 | +############################################################################## |
32 | +from . import sale |
33 | |
34 | === added file 'sale_delivery_term/__openerp__.py' |
35 | --- sale_delivery_term/__openerp__.py 1970-01-01 00:00:00 +0000 |
36 | +++ sale_delivery_term/__openerp__.py 2013-03-15 09:34:21 +0000 |
37 | @@ -0,0 +1,44 @@ |
38 | +# -*- coding: utf-8 -*- |
39 | +############################################################################## |
40 | +# |
41 | +# Copyright (C) 2012 Agile Business Group sagl (<http://www.agilebg.com>) |
42 | +# Copyright (C) 2012 Domsense srl (<http://www.domsense.com>) |
43 | +# |
44 | +# This program is free software: you can redistribute it and/or modify |
45 | +# it under the terms of the GNU Affero General Public License as published |
46 | +# by the Free Software Foundation, either version 3 of the License, or |
47 | +# (at your option) any later version. |
48 | +# |
49 | +# This program is distributed in the hope that it will be useful, |
50 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
51 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
52 | +# GNU General Public License for more details. |
53 | +# |
54 | +# You should have received a copy of the GNU Affero General Public License |
55 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
56 | +# |
57 | +############################################################################## |
58 | +{ |
59 | + 'name': "Sale delivery terms", |
60 | + 'version': '0.1', |
61 | + 'category': 'Sales Management', |
62 | + 'description': """ |
63 | +Delivery term for sale orders. |
64 | +You can configure delivery terms specifying the quantity percentage and the delay for every term line. |
65 | +You can then associate the term to the 'main' order line and generate the 'detailed' order lines which in turn will generate several pickings according to delivery term (thanks to 'sale_multi_picking' module). |
66 | +""", |
67 | + 'author': 'Agile Business Group', |
68 | + 'website': 'http://www.agilebg.com', |
69 | + 'license': 'AGPL-3', |
70 | + "depends" : ['sale_multi_picking'], |
71 | + "data" : [ |
72 | + 'sale_view.xml', |
73 | + 'security/ir.model.access.csv', |
74 | + ], |
75 | + 'test': [ |
76 | + 'test/sale_order_demo.yml', |
77 | + ], |
78 | + "demo" : ['sale_demo.xml'], |
79 | + "active": False, |
80 | + "installable": True |
81 | +} |
82 | |
83 | === added directory 'sale_delivery_term/i18n' |
84 | === added file 'sale_delivery_term/sale.py' |
85 | --- sale_delivery_term/sale.py 1970-01-01 00:00:00 +0000 |
86 | +++ sale_delivery_term/sale.py 2013-03-15 09:34:21 +0000 |
87 | @@ -0,0 +1,234 @@ |
88 | +# -*- coding: utf-8 -*- |
89 | +############################################################################## |
90 | +# |
91 | +# Copyright (C) 2012 Agile Business Group sagl (<http://www.agilebg.com>) |
92 | +# Copyright (C) 2012 Domsense srl (<http://www.domsense.com>) |
93 | +# |
94 | +# This program is free software: you can redistribute it and/or modify |
95 | +# it under the terms of the GNU Affero General Public License as published |
96 | +# by the Free Software Foundation, either version 3 of the License, or |
97 | +# (at your option) any later version. |
98 | +# |
99 | +# This program is distributed in the hope that it will be useful, |
100 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
101 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
102 | +# GNU General Public License for more details. |
103 | +# |
104 | +# You should have received a copy of the GNU Affero General Public License |
105 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
106 | +# |
107 | +############################################################################## |
108 | + |
109 | +from openerp.osv import fields, orm |
110 | +from openerp.tools.translate import _ |
111 | +import openerp.addons.decimal_precision as dp |
112 | + |
113 | +class sale_delivery_term(orm.Model): |
114 | + _name = 'sale.delivery.term' |
115 | + _columns = { |
116 | + 'name': fields.char('Name', size=64, required=True), |
117 | + 'line_ids': fields.one2many('sale.delivery.term.line', 'term_id', 'Lines', required=True), |
118 | + 'company_id': fields.many2one('res.company','Company',required=True,select=1), |
119 | + } |
120 | + _defaults = { |
121 | + 'company_id': lambda self,cr,uid,c: self.pool.get( |
122 | + 'res.company')._company_default_get(cr, uid, 'sale.delivery.term', context=c), |
123 | + } |
124 | + |
125 | + def is_total_percentage_correct(self, cr, uid, term_ids, context=None): |
126 | + for term in self.browse(cr, uid, term_ids, context=context): |
127 | + total = 0.0 |
128 | + for line in term.line_ids: |
129 | + total += line.quantity_perc |
130 | + if total != 1 : |
131 | + return False |
132 | + return True |
133 | + |
134 | +class sale_delivery_term_line(orm.Model): |
135 | + |
136 | + _name = 'sale.delivery.term.line' |
137 | + _rec_name = 'term_id' |
138 | + _columns = { |
139 | + 'term_id': fields.many2one('sale.delivery.term', 'Term'), |
140 | + 'quantity_perc': fields.float('Quantity percentage', required=True, help="For 20% set '0.2'"), |
141 | + 'delay': fields.float('Delivery Lead Time', required=True, |
142 | + help="Number of days between the order confirmation and the shipping of the products to the customer"), |
143 | + } |
144 | + |
145 | +class sale_order_line_master(orm.Model): |
146 | + |
147 | + def _clean_on_change_dict(self, res_dict): |
148 | + if res_dict['value'].has_key('delay'): |
149 | + del res_dict['value']['delay'] |
150 | + if res_dict['value'].has_key('th_weight'): |
151 | + del res_dict['value']['th_weight'] |
152 | + if res_dict['value'].has_key('type'): |
153 | + del res_dict['value']['type'] |
154 | + if res_dict['value'].has_key('tax_id'): |
155 | + del res_dict['value']['tax_id'] |
156 | + return res_dict |
157 | + |
158 | + def product_id_change(self, cr, uid, ids, pricelist, product, qty=0, |
159 | + uom=False, qty_uos=0, uos=False, name='', partner_id=False, |
160 | + lang=False, update_tax=True, date_order=False, packaging=False, |
161 | + fiscal_position=False, flag=False, context=None): |
162 | + res = self.pool.get('sale.order.line').product_id_change(cr, uid, ids, pricelist, product, qty=qty, |
163 | + uom=uom, qty_uos=qty_uos, uos=uos, name=name, partner_id=partner_id, |
164 | + lang=lang, update_tax=update_tax, date_order=date_order, |
165 | + packaging=packaging, fiscal_position=fiscal_position, flag=flag, context=context) |
166 | + return self._clean_on_change_dict(res) |
167 | + |
168 | + def product_uom_change(self, cursor, user, ids, pricelist, product, qty=0, |
169 | + uom=False, qty_uos=0, uos=False, name='', partner_id=False, |
170 | + lang=False, update_tax=True, date_order=False, context=None): |
171 | + res = self.pool.get('sale.order.line').product_uom_change(cursor, user, ids, pricelist, product, qty=qty, |
172 | + uom=uom, qty_uos=qty_uos, uos=uos, name=name, partner_id=partner_id, |
173 | + lang=lang, update_tax=update_tax, date_order=date_order, context=context) |
174 | + return self._clean_on_change_dict(res) |
175 | + |
176 | + def product_packaging_change(self, cr, uid, ids, pricelist, product, qty=0, uom=False, |
177 | + partner_id=False, packaging=False, flag=False, context=None): |
178 | + return self.pool.get('sale.order.line').product_packaging_change( |
179 | + cr, uid, ids, pricelist, product, qty=qty, uom=uom, |
180 | + partner_id=partner_id, packaging=packaging, flag=flag, context=context) |
181 | + |
182 | + def _amount_line(self, cr, uid, ids, field_name, arg, context=None): |
183 | + tax_obj = self.pool.get('account.tax') |
184 | + cur_obj = self.pool.get('res.currency') |
185 | + res = {} |
186 | + if context is None: |
187 | + context = {} |
188 | + for line in self.browse(cr, uid, ids, context=context): |
189 | + price = line.price_unit * (1 - (line.discount or 0.0) / 100.0) |
190 | + taxes = tax_obj.compute_all(cr, uid, line.tax_ids, price, |
191 | + line.product_uom_qty, line.order_id.partner_invoice_id.id, |
192 | + line.product_id, line.order_id.partner_id) |
193 | + cur = line.order_id.pricelist_id.currency_id |
194 | + res[line.id] = cur_obj.round(cr, uid, cur, taxes['total']) |
195 | + return res |
196 | + |
197 | + def _get_uom_id(self, cr, uid, *args): |
198 | + return self.pool.get('sale.order.line')._get_uom_id(cr, uid, args) |
199 | + |
200 | + _name = 'sale.order.line.master' |
201 | + _columns = { |
202 | + 'order_id': fields.many2one('sale.order', 'Order Reference', required=True, ondelete='cascade'), |
203 | + 'delivery_term_id': fields.many2one('sale.delivery.term', 'Delivery term', required=True), |
204 | + 'name': fields.char('Description', size=256, required=True), |
205 | + 'product_id': fields.many2one('product.product', 'Product', domain=[('sale_ok', '=', True)]), |
206 | + 'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Sale Price')), |
207 | + 'price_subtotal': fields.function(_amount_line, string='Subtotal', |
208 | + digits_compute= dp.get_precision('Sale Price')), |
209 | + 'product_uom_qty': fields.float('Quantity (UoM)', digits_compute= dp.get_precision('Product UoS'), required=True), |
210 | + 'product_uom': fields.many2one('product.uom', 'Unit of Measure ', required=True), |
211 | + 'product_uos_qty': fields.float('Quantity (UoS)' ,digits_compute= dp.get_precision('Product UoS')), |
212 | + 'product_uos': fields.many2one('product.uom', 'Product UoS'), |
213 | + 'product_packaging': fields.many2one('product.packaging', 'Packaging'), |
214 | + 'order_line_ids': fields.one2many('sale.order.line', 'master_line_id', 'Detailed lines'), |
215 | + 'discount': fields.float('Discount (%)', digits=(16, 2)), |
216 | + 'tax_ids': fields.many2many('account.tax', 'sale_master_order_line_tax', 'order_line_id', 'tax_id', 'Taxes'), |
217 | + } |
218 | + _defaults = { |
219 | + 'product_uom' : _get_uom_id, |
220 | + 'product_uom_qty': 1, |
221 | + 'product_uos_qty': 1, |
222 | + 'product_packaging': False, |
223 | + 'price_unit': 0.0, |
224 | + } |
225 | + |
226 | + def _prepare_order_line(self, cr, uid, term_line, master_line, group_index=0, context=None): |
227 | + order_line_pool = self.pool.get('sale.order.line') |
228 | + group_pool = self.pool.get('sale.order.line.group') |
229 | + group_ids = group_pool.search(cr, uid, []) |
230 | + product_uom_qty = master_line.product_uom_qty * term_line.quantity_perc |
231 | + product_uos_qty = master_line.product_uos_qty * term_line.quantity_perc |
232 | + order_line_vals = {} |
233 | + on_change_res = order_line_pool.product_id_change(cr, uid, [], master_line.order_id.pricelist_id.id, |
234 | + master_line.product_id.id, qty=product_uom_qty, |
235 | + uom=master_line.product_uom.id, qty_uos=product_uos_qty, |
236 | + uos=master_line.product_uos.id, name=master_line.name, partner_id=master_line.order_id.partner_id.id, |
237 | + lang=False, update_tax=True, date_order=master_line.order_id.date_order, |
238 | + packaging=master_line.product_packaging.id, fiscal_position=master_line.order_id.fiscal_position.id, |
239 | + flag=False, context=context) |
240 | + order_line_vals.update(on_change_res['value']) |
241 | + order_line_vals.update({ |
242 | + 'order_id': master_line.order_id.id, |
243 | + 'name': master_line.name, |
244 | + 'price_unit': master_line.price_unit, |
245 | + 'product_uom_qty': product_uom_qty, |
246 | + 'product_uom': master_line.product_uom.id, |
247 | + 'product_id': master_line.product_id and master_line.product_id.id or False, |
248 | + 'product_uos_qty': product_uos_qty, |
249 | + 'product_uos': master_line.product_uos and master_line.product_uos.id or False, |
250 | + 'product_packaging': master_line.product_packaging.id, |
251 | + 'master_line_id': master_line.id, |
252 | + 'delay': term_line.delay, |
253 | + 'picking_group_id': group_ids[group_index], |
254 | + 'tax_id': [(6,0, [tax.id for tax in master_line.tax_ids])], |
255 | + }) |
256 | + return order_line_vals |
257 | + |
258 | + |
259 | + def generate_detailed_lines(self, cr, uid, ids, context=None): |
260 | + group_pool = self.pool.get('sale.order.line.group') |
261 | + order_line_pool = self.pool.get('sale.order.line') |
262 | + group_ids = group_pool.search(cr, uid, []) |
263 | + for master_line in self.browse(cr, uid, ids): |
264 | + if master_line.order_line_ids: |
265 | + raise orm.except_orm(_('Error'), |
266 | + _("Detailed lines generated yet (for master line '%s'). Remove them first") % master_line.name) |
267 | + if len(master_line.delivery_term_id.line_ids) > len(group_ids): |
268 | + raise orm.except_orm(_('Error'), |
269 | + _("Delivery term lines are %d. Order line groups are %d. Please create more groups") |
270 | + % (len(master_line.delivery_term_id.line_ids), len(group_ids))) |
271 | + if not master_line.delivery_term_id.is_total_percentage_correct(): |
272 | + raise orm.except_orm(_('Error'), |
273 | + _("Total percentage of delivery term %s is not equal to 1") % master_line.delivery_term_id.name) |
274 | + for group_index, term_line in enumerate(master_line.delivery_term_id.line_ids): |
275 | + order_line_vals = self._prepare_order_line( |
276 | + cr, uid, term_line, master_line, group_index=group_index, context=context) |
277 | + order_line_pool.create(cr, uid, order_line_vals, context=context) |
278 | + return True |
279 | + |
280 | + def copy_data(self, cr, uid, id, default=None, context=None): |
281 | + if not default: |
282 | + default = {} |
283 | + default.update({ |
284 | + 'order_line_ids': [], |
285 | + }) |
286 | + return super(sale_order_line_master, self).copy_data(cr, uid, id, default, context=context) |
287 | + |
288 | + |
289 | +class sale_order_line(orm.Model): |
290 | + _inherit = 'sale.order.line' |
291 | + _columns = { |
292 | + 'master_line_id': fields.many2one('sale.order.line.master', 'Master Line'), |
293 | + } |
294 | + |
295 | + def copy_data(self, cr, uid, id, default=None, context=None): |
296 | + if not default: |
297 | + default = {} |
298 | + default.update({'master_line_id': False}) |
299 | + return super(sale_order_line, self).copy_data(cr, uid, id, default, context=context) |
300 | + |
301 | + |
302 | +class sale_order(orm.Model): |
303 | + _inherit = 'sale.order' |
304 | + _columns = { |
305 | + 'master_order_line': fields.one2many('sale.order.line.master', 'order_id', 'Master Order Lines', readonly=True, states={'draft': [('readonly', False)]}), |
306 | + } |
307 | + |
308 | + def copy(self, cr, uid, id, default=None, context=None): |
309 | + if not default: |
310 | + default = {} |
311 | + default.update({ |
312 | + 'order_line': [], |
313 | + }) |
314 | + return super(sale_order, self).copy(cr, uid, id, default, context=context) |
315 | + |
316 | + def generate_detailed_lines(self, cr, uid, ids, context=None): |
317 | + for order in self.browse(cr, uid, ids, context): |
318 | + for master_line in order.master_order_line: |
319 | + master_line.generate_detailed_lines() |
320 | + return True |
321 | + |
322 | |
323 | === added file 'sale_delivery_term/sale_demo.xml' |
324 | --- sale_delivery_term/sale_demo.xml 1970-01-01 00:00:00 +0000 |
325 | +++ sale_delivery_term/sale_demo.xml 2013-03-15 09:34:21 +0000 |
326 | @@ -0,0 +1,11 @@ |
327 | +<?xml version="1.0" encoding="utf-8"?> |
328 | +<openerp> |
329 | + <data noupdate="1"> |
330 | + |
331 | + <record id="sale_delivery_term_1" model="sale.delivery.term"> |
332 | + <field name="name">15 - 30</field> |
333 | + <field name="line_ids" eval="[(0, 0, {'quantity_perc': 0.5, 'delay': 15}),(0, 0, {'quantity_perc': 0.5, 'delay': 30})]"/> |
334 | + </record> |
335 | + |
336 | + </data> |
337 | +</openerp> |
338 | |
339 | === added file 'sale_delivery_term/sale_view.xml' |
340 | --- sale_delivery_term/sale_view.xml 1970-01-01 00:00:00 +0000 |
341 | +++ sale_delivery_term/sale_view.xml 2013-03-15 09:34:21 +0000 |
342 | @@ -0,0 +1,83 @@ |
343 | +<openerp> |
344 | + <data> |
345 | + <record id="sale_delivery_term_form" model="ir.ui.view"> |
346 | + <field name="name">sale.delivery.term.form</field> |
347 | + <field name="model">sale.delivery.term</field> |
348 | + <field name="arch" type="xml"> |
349 | + <form string="Delivery term"> |
350 | + <field name="name" select="1"/> |
351 | + <field name="line_ids" string="Term Lines" colspan="4" nolabel="1"> |
352 | + <form> |
353 | + <field name="quantity_perc"></field> |
354 | + <field name="delay"></field> |
355 | + </form> |
356 | + <tree editable="bottom"> |
357 | + <field name="quantity_perc"></field> |
358 | + <field name="delay"></field> |
359 | + </tree> |
360 | + </field> |
361 | + </form> |
362 | + </field> |
363 | + </record> |
364 | + <record id="action_delivery_term_form" model="ir.actions.act_window"> |
365 | + <field name="name">Delivery Terms</field> |
366 | + <field name="type">ir.actions.act_window</field> |
367 | + <field name="res_model">sale.delivery.term</field> |
368 | + <field name="view_type">form</field> |
369 | + <field name="view_mode">tree,form</field> |
370 | + </record> |
371 | + |
372 | + <menuitem action="action_delivery_term_form" id="menu_action_delivery_term_form" parent="base.menu_sale_config_sales" /> |
373 | + |
374 | + <record id="sale_order_line_master_form" model="ir.ui.view"> |
375 | + <field name="name">sale_order_line_master_form</field> |
376 | + <field name="model">sale.order.line.master</field> |
377 | + <field name="arch" type="xml"> |
378 | + <form string="Master order line"> |
379 | + <field name="product_id" |
380 | + on_change="product_id_change(parent.pricelist_id,product_id,product_uom_qty,product_uom,product_uos_qty,product_uos,name,parent.partner_id, False, True, parent.date_order, product_packaging, parent.fiscal_position, False, context)"/> |
381 | + <field name="name" /> |
382 | + <field name="product_uom_qty" |
383 | + on_change="product_id_change(parent.pricelist_id,product_id,product_uom_qty,product_uom,product_uos_qty,product_uos,name,parent.partner_id, False, False, parent.date_order, product_packaging, parent.fiscal_position, True, context)"/> |
384 | + <field name="product_uom" |
385 | + on_change="product_uom_change(parent.pricelist_id,product_id,product_uom_qty,product_uom,product_uos_qty,product_uos,name,parent.partner_id, False, False, parent.date_order, context)"/> |
386 | + <field name="price_unit" /> |
387 | + <field name="discount"/> |
388 | + <field name="product_uos_qty" /> |
389 | + <field name="product_uos" /> |
390 | + <field name="product_packaging" on_change="product_packaging_change(parent.pricelist_id, product_id, product_uom_qty, product_uom, parent.partner_id, product_packaging, True, context)"/> |
391 | + <field name="delivery_term_id" /> |
392 | + <separator colspan="4" string="Taxes"/> |
393 | + <field colspan="4" name="tax_ids" nolabel="1" domain="[('parent_id','=',False),('type_tax_use','<>','purchase')]"/> |
394 | + </form> |
395 | + </field> |
396 | + </record> |
397 | + <record id="sale_order_line_master_tree" model="ir.ui.view"> |
398 | + <field name="name">sale_order_line_master_tree</field> |
399 | + <field name="model">sale.order.line.master</field> |
400 | + <field name="arch" type="xml"> |
401 | + <tree string="Master order lines"> |
402 | + <field name="name" /> |
403 | + <field name="price_unit" /> |
404 | + <field name="product_uom_qty" /> |
405 | + <field name="product_uom" /> |
406 | + <field name="discount"/> |
407 | + <field name="price_subtotal" /> |
408 | + <field name="delivery_term_id" /> |
409 | + </tree> |
410 | + </field> |
411 | + </record> |
412 | + |
413 | + <record id="view_order_form" model="ir.ui.view"> |
414 | + <field name="name">sale.order.form</field> |
415 | + <field name="model">sale.order</field> |
416 | + <field name="inherit_id" ref="sale.view_order_form"></field> |
417 | + <field name="arch" type="xml"> |
418 | + <field name="order_line" position="before"> |
419 | + <field colspan="4" name="master_order_line" nolabel="1" ></field> |
420 | + <button name="generate_detailed_lines" string="Generate detailed lines" icon="gtk-go-forward" type="object" colspan="2"/> |
421 | + </field> |
422 | + </field> |
423 | + </record> |
424 | + </data> |
425 | +</openerp> |
426 | |
427 | === added directory 'sale_delivery_term/security' |
428 | === added file 'sale_delivery_term/security/ir.model.access.csv' |
429 | --- sale_delivery_term/security/ir.model.access.csv 1970-01-01 00:00:00 +0000 |
430 | +++ sale_delivery_term/security/ir.model.access.csv 2013-03-15 09:34:21 +0000 |
431 | @@ -0,0 +1,10 @@ |
432 | +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink |
433 | +access_sale_order_line_master,sale_order_line_master,model_sale_order_line_master,base.group_sale_salesman,1,1,1,1 |
434 | +access_sale_order_line_master_accountant,sale_order_line_master accountant,model_sale_order_line_master,account.group_account_user,1,1,0,0 |
435 | +access_sale_order_line_master_stock_worker,sale_order_line_master stock worker,model_sale_order_line_master,stock.group_stock_user,1,1,0,0 |
436 | +access_sale_delivery_term_sale_user,sale_delivery_term.sale.user,model_sale_delivery_term,base.group_sale_salesman,1,0,0,0 |
437 | +access_sale_delivery_term_sale_manager,sale_delivery_term.sale.manager,model_sale_delivery_term,base.group_sale_manager,1,1,1,1 |
438 | +access_sale_delivery_term,sale_delivery_term,model_sale_delivery_term,base.group_user,1,0,0,0 |
439 | +access_sale_delivery_term_line_sale_user,sale_delivery_term_line.sale.user,model_sale_delivery_term_line,base.group_sale_salesman,1,0,0,0 |
440 | +access_sale_delivery_term_line_sale_manager,sale_delivery_term_line.sale.manager,model_sale_delivery_term_line,base.group_sale_manager,1,1,1,1 |
441 | +access_sale_delivery_term_line_sale_manager,sale_delivery_term_line.sale.manager,model_sale_delivery_term_line,base.group_user,1,0,0,0 |
442 | |
443 | === added directory 'sale_delivery_term/test' |
444 | === added file 'sale_delivery_term/test/sale_order_demo.yml' |
445 | --- sale_delivery_term/test/sale_order_demo.yml 1970-01-01 00:00:00 +0000 |
446 | +++ sale_delivery_term/test/sale_order_demo.yml 2013-03-15 09:34:21 +0000 |
447 | @@ -0,0 +1,50 @@ |
448 | +- |
449 | + In order to test process of the Sale Order, I create sale order |
450 | +- |
451 | + !record {model: sale.order, id: sale_order_test1}: |
452 | + partner_id: base.res_partner_2 |
453 | + note: Invoice after delivery |
454 | + payment_term: account.account_payment_term |
455 | + master_order_line: |
456 | + - product_id: product.product_product_7 |
457 | + product_uom_qty: 8 |
458 | + delivery_term_id: sale_delivery_term_1 |
459 | +- |
460 | + I verify that the onchange was correctly triggered |
461 | +- |
462 | + !assert {model: sale.order, id: sale_order_test1, string: The onchange function of product was not correctly triggered}: |
463 | + - master_order_line[0].name == u'[LCD17] 17\u201d LCD Monitor' |
464 | + - master_order_line[0].price_unit == 1350.0 |
465 | + - master_order_line[0].product_uom_qty == 8 |
466 | + - master_order_line[0].product_uom.id == ref('product.product_uom_unit') |
467 | + |
468 | +- |
469 | + I create another sale order |
470 | +- |
471 | + !record {model: sale.order, id: sale_order_test2}: |
472 | + partner_id: base.res_partner_2 |
473 | + master_order_line: |
474 | + - product_id: product.product_product_7 |
475 | + product_uom_qty: 16 |
476 | + product_uom: product.product_uom_dozen |
477 | + delivery_term_id: sale_delivery_term_1 |
478 | +- |
479 | + I verify that the onchange was correctly triggered |
480 | +- |
481 | + !assert {model: sale.order, id: sale_order_test2, string: The onchange function of product was not correctly triggered}: |
482 | + - master_order_line[0].name == u'[LCD17] 17\u201d LCD Monitor' |
483 | + - master_order_line[0].price_unit == 1350.0 * 12 |
484 | + - master_order_line[0].product_uom.id == ref('product.product_uom_dozen') |
485 | + - master_order_line[0].product_uom_qty == 16 |
486 | + |
487 | +- |
488 | + I create the detailed order lines |
489 | +- |
490 | + !python {model: sale.order}: | |
491 | + for line in self.browse(cr, uid, ref('sale_order_test2')).master_order_line: |
492 | + line.generate_detailed_lines() |
493 | + |
494 | +- |
495 | + I confirm the Quotation |
496 | +- |
497 | + !workflow {model: sale.order, action: order_confirm, ref: sale_order_test2} |
498 | |
499 | === added directory 'sale_multi_picking' |
500 | === added file 'sale_multi_picking/AUTHORS.txt' |
501 | --- sale_multi_picking/AUTHORS.txt 1970-01-01 00:00:00 +0000 |
502 | +++ sale_multi_picking/AUTHORS.txt 2013-03-15 09:34:21 +0000 |
503 | @@ -0,0 +1,1 @@ |
504 | +Lorenzo Battistini <lorenzo.battistini@agilebg.com> |
505 | |
506 | === added file 'sale_multi_picking/__init__.py' |
507 | --- sale_multi_picking/__init__.py 1970-01-01 00:00:00 +0000 |
508 | +++ sale_multi_picking/__init__.py 2013-03-15 09:34:21 +0000 |
509 | @@ -0,0 +1,21 @@ |
510 | +# -*- coding: utf-8 -*- |
511 | +############################################################################## |
512 | +# |
513 | +# Copyright (C) 2012 Agile Business Group sagl (<http://www.agilebg.com>) |
514 | +# Copyright (C) 2012 Domsense srl (<http://www.domsense.com>) |
515 | +# |
516 | +# This program is free software: you can redistribute it and/or modify |
517 | +# it under the terms of the GNU Affero General Public License as published |
518 | +# by the Free Software Foundation, either version 3 of the License, or |
519 | +# (at your option) any later version. |
520 | +# |
521 | +# This program is distributed in the hope that it will be useful, |
522 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
523 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
524 | +# GNU General Public License for more details. |
525 | +# |
526 | +# You should have received a copy of the GNU Affero General Public License |
527 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
528 | +# |
529 | +############################################################################## |
530 | +from . import sale |
531 | |
532 | === added file 'sale_multi_picking/__openerp__.py' |
533 | --- sale_multi_picking/__openerp__.py 1970-01-01 00:00:00 +0000 |
534 | +++ sale_multi_picking/__openerp__.py 2013-03-15 09:34:21 +0000 |
535 | @@ -0,0 +1,42 @@ |
536 | +# -*- coding: utf-8 -*- |
537 | +############################################################################## |
538 | +# |
539 | +# Copyright (C) 2012 Agile Business Group sagl (<http://www.agilebg.com>) |
540 | +# Copyright (C) 2012 Domsense srl (<http://www.domsense.com>) |
541 | +# |
542 | +# This program is free software: you can redistribute it and/or modify |
543 | +# it under the terms of the GNU Affero General Public License as published |
544 | +# by the Free Software Foundation, either version 3 of the License, or |
545 | +# (at your option) any later version. |
546 | +# |
547 | +# This program is distributed in the hope that it will be useful, |
548 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
549 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
550 | +# GNU General Public License for more details. |
551 | +# |
552 | +# You should have received a copy of the GNU Affero General Public License |
553 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
554 | +# |
555 | +############################################################################## |
556 | +{ |
557 | + 'name': "Multi Pickings from Sale Orders", |
558 | + 'version': '0.1', |
559 | + 'category': 'Sales Management', |
560 | + 'description': """ |
561 | +This module allows to generate several pickings from the same sale order. |
562 | +You just have to indicate which order lines have to be grouped in the same picking. When confirming the order, for each group a picking is generated. |
563 | +""", |
564 | + 'author': 'Agile Business Group', |
565 | + 'website': 'http://www.agilebg.com', |
566 | + 'license': 'AGPL-3', |
567 | + "depends" : ['sale_stock'], |
568 | + "data" : [ |
569 | + 'sale_view.xml', |
570 | + 'security/ir.model.access.csv', |
571 | + ], |
572 | + "demo" : [ |
573 | + 'sale_demo.xml', |
574 | + ], |
575 | + "active": False, |
576 | + "installable": True |
577 | +} |
578 | |
579 | === added directory 'sale_multi_picking/i18n' |
580 | === added file 'sale_multi_picking/sale.py' |
581 | --- sale_multi_picking/sale.py 1970-01-01 00:00:00 +0000 |
582 | +++ sale_multi_picking/sale.py 2013-03-15 09:34:21 +0000 |
583 | @@ -0,0 +1,63 @@ |
584 | +# -*- coding: utf-8 -*- |
585 | +############################################################################## |
586 | +# |
587 | +# Copyright (C) 2012 Agile Business Group sagl (<http://www.agilebg.com>) |
588 | +# Copyright (C) 2012 Domsense srl (<http://www.domsense.com>) |
589 | +# |
590 | +# This program is free software: you can redistribute it and/or modify |
591 | +# it under the terms of the GNU Affero General Public License as published |
592 | +# by the Free Software Foundation, either version 3 of the License, or |
593 | +# (at your option) any later version. |
594 | +# |
595 | +# This program is distributed in the hope that it will be useful, |
596 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
597 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
598 | +# GNU General Public License for more details. |
599 | +# |
600 | +# You should have received a copy of the GNU Affero General Public License |
601 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
602 | +# |
603 | +############################################################################## |
604 | + |
605 | +from openerp.osv import fields, orm |
606 | + |
607 | +class sale_order_line_group(orm.Model): |
608 | + _name = 'sale.order.line.group' |
609 | + _columns = { |
610 | + 'name': fields.char('Group', size=64, required=True), |
611 | + 'company_id': fields.many2one('res.company','Company',required=True,select=1), |
612 | + } |
613 | + _defaults = { |
614 | + 'company_id': lambda self,cr,uid,c: self.pool.get( |
615 | + 'res.company')._company_default_get(cr, uid, 'sale.order.line.group', context=c), |
616 | + } |
617 | + |
618 | + |
619 | +class sale_order_line(orm.Model): |
620 | + _inherit = 'sale.order.line' |
621 | + _columns = { |
622 | + 'picking_group_id': fields.many2one('sale.order.line.group', 'Group', |
623 | + help="This is used by 'multi-picking' to group order lines in one picking"), |
624 | + } |
625 | + |
626 | + |
627 | +class sale_order(orm.Model): |
628 | + _inherit = 'sale.order' |
629 | + |
630 | + def action_ship_create(self, cr, uid, ids, context=None): |
631 | + picking_pool = self.pool.get('stock.picking') |
632 | + for order in self.browse(cr, uid, ids, context=context): |
633 | + lines_by_group = {} |
634 | + for line in order.order_line: |
635 | + group_id = line.picking_group_id.id if line.picking_group_id else 0 |
636 | + lines_by_group.setdefault(group_id, []).append(line) |
637 | + for group in lines_by_group: |
638 | + if not group: |
639 | + picking_id = None |
640 | + else: |
641 | + picking_vals = super(sale_order, self)._prepare_order_picking(cr, uid, order, context=context) |
642 | + picking_id = picking_pool.create(cr, uid, picking_vals, context=context) |
643 | + super(sale_order, self)._create_pickings_and_procurements( |
644 | + cr, uid, order, lines_by_group[group], picking_id, context=context) |
645 | + return True |
646 | + |
647 | |
648 | === added file 'sale_multi_picking/sale_demo.xml' |
649 | --- sale_multi_picking/sale_demo.xml 1970-01-01 00:00:00 +0000 |
650 | +++ sale_multi_picking/sale_demo.xml 2013-03-15 09:34:21 +0000 |
651 | @@ -0,0 +1,13 @@ |
652 | +<?xml version="1.0" encoding="utf-8"?> |
653 | +<openerp> |
654 | + <data noupdate="1"> |
655 | + |
656 | + <record id="sale_order_line_group_1" model="sale.order.line.group"> |
657 | + <field name="name">1</field> |
658 | + </record> |
659 | + <record id="sale_order_line_group_2" model="sale.order.line.group"> |
660 | + <field name="name">2</field> |
661 | + </record> |
662 | + |
663 | + </data> |
664 | +</openerp> |
665 | |
666 | === added file 'sale_multi_picking/sale_view.xml' |
667 | --- sale_multi_picking/sale_view.xml 1970-01-01 00:00:00 +0000 |
668 | +++ sale_multi_picking/sale_view.xml 2013-03-15 09:34:21 +0000 |
669 | @@ -0,0 +1,23 @@ |
670 | +<openerp> |
671 | + <data> |
672 | + <record id="view_order_form" model="ir.ui.view"> |
673 | + <field name="name">sale.order.form</field> |
674 | + <field name="model">sale.order</field> |
675 | + <field name="inherit_id" ref="sale.view_order_form"></field> |
676 | + <field name="arch" type="xml"> |
677 | + <field name="address_allotment_id" position="after"> |
678 | + <field name="picking_group_id"/> |
679 | + </field> |
680 | + </field> |
681 | + </record> |
682 | + <record id="action_sale_order_line_group_form" model="ir.actions.act_window"> |
683 | + <field name="name">Sale order line Groups</field> |
684 | + <field name="type">ir.actions.act_window</field> |
685 | + <field name="res_model">sale.order.line.group</field> |
686 | + <field name="view_type">form</field> |
687 | + <field name="view_mode">tree,form</field> |
688 | + </record> |
689 | + |
690 | + <menuitem action="action_sale_order_line_group_form" id="menu_action_sale_order_line_group_form" parent="base.menu_sale_config_sales" /> |
691 | + </data> |
692 | +</openerp> |
693 | |
694 | === added directory 'sale_multi_picking/security' |
695 | === added file 'sale_multi_picking/security/ir.model.access.csv' |
696 | --- sale_multi_picking/security/ir.model.access.csv 1970-01-01 00:00:00 +0000 |
697 | +++ sale_multi_picking/security/ir.model.access.csv 2013-03-15 09:34:21 +0000 |
698 | @@ -0,0 +1,4 @@ |
699 | +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink |
700 | +access_sale_order_line_group,sale.order.line.group,model_sale_order_line_group,base.group_sale_salesman,1,1,1,1 |
701 | +access_sale_order_line_group_accountant,sale.order.line.group accountant,model_sale_order_line_group,account.group_account_user,1,1,0,0 |
702 | +access_sale_order_line_group_stock_worker,sale.order.line.group stock worker,model_sale_order_line_group,stock.group_stock_user,1,1,0,0 |
When porting a module to V7, please:
* update __openerp__.py to use 'data' instead of 'init_xml' and 'update_xml', and 'demo' instead of 'demo_xml'
* update you python code to inherit from orm.Model (resp orm.TransientModel) rather than osv.osv (resp. osv.osv_memory)
* update you python code tonot instantiate the model class
Try to keep line length reasonable. Ideally, 80 chars is great, but not always the most convenient. Several lines here are way too long.
l. 32 and 439: use an explicit relative import (from . import sale)
l. 248: use %d in the format string, and remove the str() calls (btw, %s will call str() implicitely)
l. 539-548: this can be rewritten in a much clearer way as follows:
lines_by_group = {} group_id. id if line.picking_ group_id else 0 by_group. setdefault( group_id, []).append(line)
for line in order.order_line:
group_id = line.picking_
lines_
line 549: you could factor out the call to super() ._create_ pickings_ and_procurement s, by just setting picking_id to None in the if branch.