Merge lp:~elbati/sale-wkfl/adding_sale_delivery_term_7 into lp:~sale-core-editors/sale-wkfl/7.0

Proposed by Lorenzo Battistini
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
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

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_picking' module).

To post a comment you must log in.
Revision history for this message
Alexandre Fayolle - camptocamp (alexandre-fayolle-c2c) wrote :

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 = {}
for line in order.order_line:
    group_id = line.picking_group_id.id if line.picking_group_id else 0
    lines_by_group.setdefault(group_id, []).append(line)

line 549: you could factor out the call to super()._create_pickings_and_procurements, by just setting picking_id to None in the if branch.

review: Needs Fixing (code review, no test)
11. By Lorenzo Battistini

[FIX] __openerp__.py

Revision history for this message
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?

Revision history for this message
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://code.launchpad.net/~camptocamp/openobject-server/trunk-proposed-cleanups/+merge/102833/comments/269356
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://www.camptocamp.com

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_procurements

Revision history for this message
Lorenzo Battistini (elbati) wrote :

Made the changes.
Thanks

18. By Lorenzo Battistini

[IMP] enumerate

19. By Lorenzo Battistini

[FIX] string delimiter

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

LGTM

review: Approve (code review, no test)
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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','&lt;&gt;','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

Subscribers

People subscribed via source and target branches