[IMP] removal of usages of the deprecated node.getchildren call, better usage of...
[odoo/odoo.git] / addons / sale / sale.py
1 # -*- encoding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
6 #    $Id$
7 #
8 #    This program is free software: you can redistribute it and/or modify
9 #    it under the terms of the GNU General Public License as published by
10 #    the Free Software Foundation, either version 3 of the License, or
11 #    (at your option) any later version.
12 #
13 #    This program is distributed in the hope that it will be useful,
14 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 #    GNU General Public License for more details.
17 #
18 #    You should have received a copy of the GNU General Public License
19 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 #
21 ##############################################################################
22
23 import time
24 import netsvc
25 from osv import fields, osv
26 from mx import DateTime
27 from tools import config
28 from tools.translate import _
29
30
31 class sale_shop(osv.osv):
32     _name = "sale.shop"
33     _description = "Sale Shop"
34     _columns = {
35         'name': fields.char('Shop Name', size=64, required=True),
36         'payment_default_id': fields.many2one('account.payment.term', 'Default Payment Term', required=True),
37         'payment_account_id': fields.many2many('account.account', 'sale_shop_account', 'shop_id', 'account_id', 'Payment Accounts'),
38         'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse'),
39         'pricelist_id': fields.many2one('product.pricelist', 'Pricelist'),
40         'project_id': fields.many2one('account.analytic.account', 'Analytic Account'),
41     }
42 sale_shop()
43
44
45 def _incoterm_get(self, cr, uid, context={}):
46     cr.execute('select code, code||\', \'||name from stock_incoterms where active')
47     return cr.fetchall()
48
49
50 class sale_order(osv.osv):
51     _name = "sale.order"
52     _description = "Sale Order"
53
54     def copy(self, cr, uid, id, default=None, context={}):
55         if not default:
56             default = {}
57         default.update({
58             'state': 'draft',
59             'shipped': False,
60             'invoice_ids': [],
61             'picking_ids': [],
62             'name': self.pool.get('ir.sequence').get(cr, uid, 'sale.order'),
63         })
64         return super(sale_order, self).copy(cr, uid, id, default, context)
65
66     def _amount_line_tax(self, cr, uid, line, context={}):
67         val = 0.0
68         for c in self.pool.get('account.tax').compute(cr, uid, line.tax_id, line.price_unit * (1-(line.discount or 0.0)/100.0), line.product_uom_qty, line.order_id.partner_invoice_id.id, line.product_id, line.order_id.partner_id):
69             val += c['amount']
70         return val
71
72     def _amount_all(self, cr, uid, ids, field_name, arg, context):
73         res = {}
74         cur_obj = self.pool.get('res.currency')
75         for order in self.browse(cr, uid, ids):
76             res[order.id] = {
77                 'amount_untaxed': 0.0,
78                 'amount_tax': 0.0,
79                 'amount_total': 0.0,
80             }
81             val = val1 = 0.0
82             cur = order.pricelist_id.currency_id
83             for line in order.order_line:
84                 val1 += line.price_subtotal
85                 val += self._amount_line_tax(cr, uid, line, context)
86             res[order.id]['amount_tax'] = cur_obj.round(cr, uid, cur, val)
87             res[order.id]['amount_untaxed'] = cur_obj.round(cr, uid, cur, val1)
88             res[order.id]['amount_total'] = res[order.id]['amount_untaxed'] + res[order.id]['amount_tax']
89         return res
90
91     def _picked_rate(self, cr, uid, ids, name, arg, context=None):
92         if not ids:
93             return {}
94         res = {}
95         for id in ids:
96             res[id] = [0.0, 0.0]
97         cr.execute('''SELECT
98                 p.sale_id,sum(m.product_qty), m.state
99             FROM
100                 stock_move m
101             LEFT JOIN
102                 stock_picking p on (p.id=m.picking_id)
103             WHERE
104                 p.sale_id in ('''+','.join(map(str, ids))+''')
105             GROUP BY m.state, p.sale_id''')
106         for oid, nbr, state in cr.fetchall():
107             if state == 'cancel':
108                 continue
109             if state == 'done':
110                 res[oid][0] += nbr or 0.0
111                 res[oid][1] += nbr or 0.0
112             else:
113                 res[oid][1] += nbr or 0.0
114         for r in res:
115             if not res[r][1]:
116                 res[r] = 0.0
117             else:
118                 res[r] = 100.0 * res[r][0] / res[r][1]
119         for order in self.browse(cr, uid, ids, context):
120             if order.shipped:
121                 res[order.id] = 100.0
122         return res
123
124     def _invoiced_rate(self, cursor, user, ids, name, arg, context=None):
125         res = {}
126         for sale in self.browse(cursor, user, ids, context=context):
127             if sale.invoiced:
128                 res[sale.id] = 100.0
129                 continue
130             tot = 0.0
131             for invoice in sale.invoice_ids:
132                 if invoice.state not in ('draft', 'cancel'):
133                     tot += invoice.amount_untaxed
134
135             if tot:
136                 res[sale.id] = min(100.0, tot * 100.0 / (sale.amount_untaxed or 1.00))
137             else:
138                 res[sale.id] = 0.0
139         return res
140
141     def _invoiced(self, cursor, user, ids, name, arg, context=None):
142         res = {}
143         for sale in self.browse(cursor, user, ids, context=context):
144             res[sale.id] = True
145             for invoice in sale.invoice_ids:
146                 if invoice.state != 'paid':
147                     res[sale.id] = False
148                     break
149             if not sale.invoice_ids:
150                 res[sale.id] = False
151         return res
152
153     def _invoiced_search(self, cursor, user, obj, name, args):
154         if not len(args):
155             return []
156         clause = ''
157         no_invoiced = False
158         for arg in args:
159             if arg[1] == '=':
160                 if arg[2]:
161                     clause += 'AND inv.state = \'paid\''
162                 else:
163                     clause += 'AND inv.state <> \'paid\''
164                     no_invoiced = True
165         cursor.execute('SELECT rel.order_id ' \
166                 'FROM sale_order_invoice_rel AS rel, account_invoice AS inv ' \
167                 'WHERE rel.invoice_id = inv.id ' + clause)
168         res = cursor.fetchall()
169         if no_invoiced:
170             cursor.execute('SELECT sale.id ' \
171                     'FROM sale_order AS sale ' \
172                     'WHERE sale.id NOT IN ' \
173                         '(SELECT rel.order_id ' \
174                         'FROM sale_order_invoice_rel AS rel)')
175             res.extend(cursor.fetchall())
176         if not res:
177             return [('id', '=', 0)]
178         return [('id', 'in', [x[0] for x in res])]
179
180     def _get_order(self, cr, uid, ids, context={}):
181         result = {}
182         for line in self.pool.get('sale.order.line').browse(cr, uid, ids, context=context):
183             result[line.order_id.id] = True
184         return result.keys()
185
186     _columns = {
187         'name': fields.char('Order Reference', size=64, required=True, select=True),
188         'shop_id': fields.many2one('sale.shop', 'Shop', required=True, readonly=True, states={'draft': [('readonly', False)]}),
189         'origin': fields.char('Origin', size=64),
190         'client_order_ref': fields.char('Customer Ref', size=64),
191
192         'state': fields.selection([
193             ('draft', 'Quotation'),
194             ('waiting_date', 'Waiting Schedule'),
195             ('manual', 'Manual In Progress'),
196             ('progress', 'In Progress'),
197             ('shipping_except', 'Shipping Exception'),
198             ('invoice_except', 'Invoice Exception'),
199             ('done', 'Done'),
200             ('cancel', 'Cancelled')
201             ], 'Order State', readonly=True, help="Gives the state of the quotation or sale order. The exception state is automatically set when a cancel operation occurs in the invoice validation (Invoice Exception) or in the packing list process (Shipping Exception). The 'Waiting Schedule' state is set when the invoice is confirmed but waiting for the scheduler to run on the date 'Date Ordered'.", select=True),
202         'date_order': fields.date('Date Ordered', required=True, readonly=True, states={'draft': [('readonly', False)]}),
203
204         'user_id': fields.many2one('res.users', 'Salesman', states={'draft': [('readonly', False)]}, select=True),
205         'partner_id': fields.many2one('res.partner', 'Customer', readonly=True, states={'draft': [('readonly', False)]}, required=True, change_default=True, select=True),
206         'partner_invoice_id': fields.many2one('res.partner.address', 'Invoice Address', readonly=True, required=True, states={'draft': [('readonly', False)]}),
207         'partner_order_id': fields.many2one('res.partner.address', 'Ordering Contact', readonly=True, required=True, states={'draft': [('readonly', False)]}, help="The name and address of the contact that requested the order or quotation."),
208         'partner_shipping_id': fields.many2one('res.partner.address', 'Shipping Address', readonly=True, required=True, states={'draft': [('readonly', False)]}),
209
210         'incoterm': fields.selection(_incoterm_get, 'Incoterm', size=3),
211         'picking_policy': fields.selection([('direct', 'Partial Delivery'), ('one', 'Complete Delivery')],
212             'Packing Policy', required=True, help="""If you don't have enough stock available to deliver all at once, do you accept partial shipments or not?"""),
213         'order_policy': fields.selection([
214             ('prepaid', 'Payment Before Delivery'),
215             ('manual', 'Shipping & Manual Invoice'),
216             ('postpaid', 'Invoice on Order After Delivery'),
217             ('picking', 'Invoice from the Packing'),
218         ], 'Shipping Policy', required=True, readonly=True, states={'draft': [('readonly', False)]},
219                     help="""The Shipping Policy is used to synchronise invoice and delivery operations.
220   - The 'Pay before delivery' choice will first generate the invoice and then generate the packing order after the payment of this invoice.
221   - The 'Shipping & Manual Invoice' will create the packing order directly and wait for the user to manually click on the 'Invoice' button to generate the draft invoice.
222   - The 'Invoice on Order After Delivery' choice will generate the draft invoice based on sale order after all packing lists have been finished.
223   - The 'Invoice from the packing' choice is used to create an invoice during the packing process."""),
224         'pricelist_id': fields.many2one('product.pricelist', 'Pricelist', required=True, readonly=True, states={'draft': [('readonly', False)]}),
225         'project_id': fields.many2one('account.analytic.account', 'Analytic Account', readonly=True, states={'draft': [('readonly', False)]}),
226
227         'order_line': fields.one2many('sale.order.line', 'order_id', 'Order Lines', readonly=True, states={'draft': [('readonly', False)]}),
228         'invoice_ids': fields.many2many('account.invoice', 'sale_order_invoice_rel', 'order_id', 'invoice_id', 'Invoices', help="This is the list of invoices that have been generated for this sale order. The same sale order may have been invoiced in several times (by line for example)."),
229         'picking_ids': fields.one2many('stock.picking', 'sale_id', 'Related Packing', readonly=True, help="This is the list of picking list that have been generated for this invoice"),
230         'shipped': fields.boolean('Picked', readonly=True),
231         'picked_rate': fields.function(_picked_rate, method=True, string='Picked', type='float'),
232         'invoiced_rate': fields.function(_invoiced_rate, method=True, string='Invoiced', type='float'),
233         'invoiced': fields.function(_invoiced, method=True, string='Paid',
234             fnct_search=_invoiced_search, type='boolean'),
235         'note': fields.text('Notes'),
236
237         'amount_untaxed': fields.function(_amount_all, method=True, digits=(16, int(config['price_accuracy'])), string='Untaxed Amount',
238             store = {
239                 'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line'], 10),
240                 'sale.order.line': (_get_order, ['price_unit', 'tax_id', 'discount', 'product_uom_qty'], 10),
241             },
242             multi='sums'),
243         'amount_tax': fields.function(_amount_all, method=True, digits=(16, int(config['price_accuracy'])), string='Taxes',
244             store = {
245                 'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line'], 10),
246                 'sale.order.line': (_get_order, ['price_unit', 'tax_id', 'discount', 'product_uom_qty'], 10),
247             },
248             multi='sums'),
249         'amount_total': fields.function(_amount_all, method=True, digits=(16, int(config['price_accuracy'])), string='Total',
250             store = {
251                 'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line'], 10),
252                 'sale.order.line': (_get_order, ['price_unit', 'tax_id', 'discount', 'product_uom_qty'], 10),
253             },
254             multi='sums'),
255
256         'invoice_quantity': fields.selection([('order', 'Ordered Quantities'), ('procurement', 'Shipped Quantities')], 'Invoice on', help="The sale order will automatically create the invoice proposition (draft invoice). Ordered and delivered quantities may not be the same. You have to choose if you invoice based on ordered or shipped quantities. If the product is a service, shipped quantities means hours spent on the associated tasks.", required=True),
257         'payment_term': fields.many2one('account.payment.term', 'Payment Term'),
258         'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position')
259     }
260     _defaults = {
261         'picking_policy': lambda *a: 'direct',
262         'date_order': lambda *a: time.strftime('%Y-%m-%d'),
263         'order_policy': lambda *a: 'manual',
264         'state': lambda *a: 'draft',
265         'user_id': lambda obj, cr, uid, context: uid,
266         'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'sale.order'),
267         'invoice_quantity': lambda *a: 'order',
268         'partner_invoice_id': lambda self, cr, uid, context: context.get('partner_id', False) and self.pool.get('res.partner').address_get(cr, uid, [context['partner_id']], ['invoice'])['invoice'],
269         'partner_order_id': lambda self, cr, uid, context: context.get('partner_id', False) and  self.pool.get('res.partner').address_get(cr, uid, [context['partner_id']], ['contact'])['contact'],
270         'partner_shipping_id': lambda self, cr, uid, context: context.get('partner_id', False) and self.pool.get('res.partner').address_get(cr, uid, [context['partner_id']], ['delivery'])['delivery'],
271         'pricelist_id': lambda self, cr, uid, context: context.get('partner_id', False) and self.pool.get('res.partner').browse(cr, uid, context['partner_id']).property_product_pricelist.id,
272     }
273     _order = 'name desc'
274
275     # Form filling
276     def unlink(self, cr, uid, ids, context=None):
277         sale_orders = self.read(cr, uid, ids, ['state'])
278         unlink_ids = []
279         for s in sale_orders:
280             if s['state'] in ['draft', 'cancel']:
281                 unlink_ids.append(s['id'])
282             else:
283                 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Sale Order(s) which are already confirmed !'))
284         return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
285
286     def onchange_shop_id(self, cr, uid, ids, shop_id):
287         v = {}
288         if shop_id:
289             shop = self.pool.get('sale.shop').browse(cr, uid, shop_id)
290             v['project_id'] = shop.project_id.id
291             # Que faire si le client a une pricelist a lui ?
292             if shop.pricelist_id.id:
293                 v['pricelist_id'] = shop.pricelist_id.id
294             #v['payment_default_id']=shop.payment_default_id.id
295         return {'value': v}
296
297     def action_cancel_draft(self, cr, uid, ids, *args):
298         if not len(ids):
299             return False
300         cr.execute('select id from sale_order_line where order_id in ('+','.join(map(str, ids))+')', ('draft',))
301         line_ids = map(lambda x: x[0], cr.fetchall())
302         self.write(cr, uid, ids, {'state': 'draft', 'invoice_ids': [], 'shipped': 0})
303         self.pool.get('sale.order.line').write(cr, uid, line_ids, {'invoiced': False, 'state': 'draft', 'invoice_lines': [(6, 0, [])]})
304         wf_service = netsvc.LocalService("workflow")
305         for inv_id in ids:
306             wf_service.trg_create(uid, 'sale.order', inv_id, cr)
307         return True
308
309     def onchange_partner_id(self, cr, uid, ids, part):
310         if not part:
311             return {'value': {'partner_invoice_id': False, 'partner_shipping_id': False, 'partner_order_id': False, 'payment_term': False, 'fiscal_position': False}}
312         addr = self.pool.get('res.partner').address_get(cr, uid, [part], ['delivery', 'invoice', 'contact'])
313         part = self.pool.get('res.partner').browse(cr, uid, part)
314         pricelist = part.property_product_pricelist and part.property_product_pricelist.id or False
315         payment_term = part.property_payment_term and part.property_payment_term.id or False
316         fiscal_position = part.property_account_position and part.property_account_position.id or False
317         val = {'partner_invoice_id': addr['invoice'], 'partner_order_id': addr['contact'], 'partner_shipping_id': addr['delivery'], 'payment_term': payment_term, 'fiscal_position': fiscal_position}
318         if pricelist:
319             val['pricelist_id'] = pricelist
320         return {'value': val}
321
322     def shipping_policy_change(self, cr, uid, ids, policy, context={}):
323         if not policy:
324             return {}
325         inv_qty = 'order'
326         if policy == 'prepaid':
327             inv_qty = 'order'
328         elif policy == 'picking':
329             inv_qty = 'procurement'
330         return {'value': {'invoice_quantity': inv_qty}}
331
332     def write(self, cr, uid, ids, vals, context=None):
333         if 'order_policy' in vals:
334             if vals['order_policy'] == 'prepaid':
335                 vals.update({'invoice_quantity': 'order'})
336             elif vals['order_policy'] == 'picking':
337                 vals.update({'invoice_quantity': 'procurement'})
338         return super(sale_order, self).write(cr, uid, ids, vals, context=context)
339
340     def create(self, cr, uid, vals, context={}):
341         if 'order_policy' in vals:
342             if vals['order_policy'] == 'prepaid':
343                 vals.update({'invoice_quantity': 'order'})
344             if vals['order_policy'] == 'picking':
345                 vals.update({'invoice_quantity': 'procurement'})
346         return super(sale_order, self).create(cr, uid, vals, context=context)
347
348     def button_dummy(self, cr, uid, ids, context={}):
349         return True
350
351 #FIXME: the method should return the list of invoices created (invoice_ids)
352 # and not the id of the last invoice created (res). The problem is that we
353 # cannot change it directly since the method is called by the sale order
354 # workflow and I suppose it expects a single id...
355     def _inv_get(self, cr, uid, order, context={}):
356         return {}
357
358     def _make_invoice(self, cr, uid, order, lines, context={}):
359         a = order.partner_id.property_account_receivable.id
360         if order.payment_term:
361             pay_term = order.payment_term.id
362         else:
363             pay_term = False
364         for preinv in order.invoice_ids:
365             if preinv.state not in ('cancel',):
366                 for preline in preinv.invoice_line:
367                     inv_line_id = self.pool.get('account.invoice.line').copy(cr, uid, preline.id, {'invoice_id': False, 'price_unit': -preline.price_unit})
368                     lines.append(inv_line_id)
369         inv = {
370             'name': order.client_order_ref or order.name,
371             'origin': order.name,
372             'type': 'out_invoice',
373             'reference': "P%dSO%d" % (order.partner_id.id, order.id),
374             'account_id': a,
375             'partner_id': order.partner_id.id,
376             'address_invoice_id': order.partner_invoice_id.id,
377             'address_contact_id': order.partner_order_id.id,
378             'invoice_line': [(6, 0, lines)],
379             'currency_id': order.pricelist_id.currency_id.id,
380             'comment': order.note,
381             'payment_term': pay_term,
382             'fiscal_position': order.partner_id.property_account_position.id
383         }
384         inv_obj = self.pool.get('account.invoice')
385         inv.update(self._inv_get(cr, uid, order))
386         inv_id = inv_obj.create(cr, uid, inv)
387         data = inv_obj.onchange_payment_term_date_invoice(cr, uid, [inv_id], pay_term, time.strftime('%Y-%m-%d'))
388         if data.get('value', False):
389             inv_obj.write(cr, uid, [inv_id], data['value'], context=context)
390         inv_obj.button_compute(cr, uid, [inv_id])
391         return inv_id
392
393     def action_invoice_create(self, cr, uid, ids, grouped=False, states=['confirmed', 'done', 'exception']):
394         res = False
395         invoices = {}
396         invoice_ids = []
397
398         for o in self.browse(cr, uid, ids):
399             lines = []
400             for line in o.order_line:
401                 if (line.state in states) and not line.invoiced:
402                     lines.append(line.id)
403             created_lines = self.pool.get('sale.order.line').invoice_line_create(cr, uid, lines)
404             if created_lines:
405                 invoices.setdefault(o.partner_id.id, []).append((o, created_lines))
406
407         if not invoices:
408             for o in self.browse(cr, uid, ids):
409                 for i in o.invoice_ids:
410                     if i.state == 'draft':
411                         return i.id
412         picking_obj = self.pool.get('stock.picking')
413         for val in invoices.values():
414             if grouped:
415                 res = self._make_invoice(cr, uid, val[0][0], reduce(lambda x, y: x + y, [l for o, l in val], []))
416                 for o, l in val:
417                     self.write(cr, uid, [o.id], {'state': 'progress'})
418                     if o.order_policy == 'picking':
419                         picking_obj.write(cr, uid, map(lambda x: x.id, o.picking_ids), {'invoice_state': 'invoiced'})
420                     cr.execute('insert into sale_order_invoice_rel (order_id,invoice_id) values (%s,%s)', (o.id, res))
421             else:
422                 for order, il in val:
423                     res = self._make_invoice(cr, uid, order, il)
424                     invoice_ids.append(res)
425                     self.write(cr, uid, [order.id], {'state': 'progress'})
426                     if order.order_policy == 'picking':
427                         picking_obj.write(cr, uid, map(lambda x: x.id, order.picking_ids), {'invoice_state': 'invoiced'})
428                     cr.execute('insert into sale_order_invoice_rel (order_id,invoice_id) values (%s,%s)', (order.id, res))
429         return res
430
431     def action_invoice_cancel(self, cr, uid, ids, context={}):
432         for sale in self.browse(cr, uid, ids):
433             for line in sale.order_line:
434                 invoiced = False
435                 for iline in line.invoice_lines:
436                     if iline.invoice_id and iline.invoice_id.state == 'cancel':
437                         continue
438                     else:
439                         invoiced = True
440                 self.pool.get('sale.order.line').write(cr, uid, [line.id], {'invoiced': invoiced})
441         self.write(cr, uid, ids, {'state': 'invoice_except', 'invoice_ids': False})
442         return True
443
444     def action_cancel(self, cr, uid, ids, context={}):
445         ok = True
446         sale_order_line_obj = self.pool.get('sale.order.line')
447         for sale in self.browse(cr, uid, ids):
448             for pick in sale.picking_ids:
449                 if pick.state not in ('draft', 'cancel'):
450                     raise osv.except_osv(
451                         _('Could not cancel sale order !'),
452                         _('You must first cancel all packing attached to this sale order.'))
453             for r in self.read(cr, uid, ids, ['picking_ids']):
454                 for pick in r['picking_ids']:
455                     wf_service = netsvc.LocalService("workflow")
456                     wf_service.trg_validate(uid, 'stock.picking', pick, 'button_cancel', cr)
457             for inv in sale.invoice_ids:
458                 if inv.state not in ('draft', 'cancel'):
459                     raise osv.except_osv(
460                         _('Could not cancel this sale order !'),
461                         _('You must first cancel all invoices attached to this sale order.'))
462             for r in self.read(cr, uid, ids, ['invoice_ids']):
463                 for inv in r['invoice_ids']:
464                     wf_service = netsvc.LocalService("workflow")
465                     wf_service.trg_validate(uid, 'account.invoice', inv, 'invoice_cancel', cr)
466             sale_order_line_obj.write(cr, uid, [l.id for l in  sale.order_line],
467                     {'state': 'cancel'})
468         self.write(cr, uid, ids, {'state': 'cancel'})
469         return True
470
471     def action_wait(self, cr, uid, ids, *args):
472         event_p = self.pool.get('res.partner.event.type').check(cr, uid, 'sale_open')
473         event_obj = self.pool.get('res.partner.event')
474         for o in self.browse(cr, uid, ids):
475             if event_p:
476                 event_obj.create(cr, uid, {'name': 'Sale Order: '+ o.name,\
477                         'partner_id': o.partner_id.id,\
478                         'date': time.strftime('%Y-%m-%d %H:%M:%S'),\
479                         'user_id': (o.user_id and o.user_id.id) or uid,\
480                         'partner_type': 'customer', 'probability': 1.0,\
481                         'planned_revenue': o.amount_untaxed})
482             if (o.order_policy == 'manual'):
483                 self.write(cr, uid, [o.id], {'state': 'manual'})
484             else:
485                 self.write(cr, uid, [o.id], {'state': 'progress'})
486             self.pool.get('sale.order.line').button_confirm(cr, uid, [x.id for x in o.order_line])
487
488     def procurement_lines_get(self, cr, uid, ids, *args):
489         res = []
490         for order in self.browse(cr, uid, ids, context={}):
491             for line in order.order_line:
492                 if line.procurement_id:
493                     res.append(line.procurement_id.id)
494         return res
495         
496     # if mode == 'finished':
497     #   returns True if all lines are done, False otherwise
498     # if mode == 'canceled':
499     #   returns True if there is at least one canceled line, False otherwise
500     def test_state(self, cr, uid, ids, mode, *args):
501         assert mode in ('finished', 'canceled'), _("invalid mode for test_state")
502         finished = True
503         canceled = False
504         notcanceled = False
505         write_done_ids = []
506         write_cancel_ids = []
507         stock_move_obj = self.pool.get('stock.move')
508         for order in self.browse(cr, uid, ids, context={}):
509             
510             #check for pending deliveries 
511             pending_deliveries = False
512             # check => if order_lines do not exist,don't proceed for any mode.
513             if not order.order_line:
514                 return False
515             for line in order.order_line:    
516                 move_ids = stock_move_obj.search(cr, uid, [('sale_line_id','=', line.id)])
517                 for move in stock_move_obj.browse( cr, uid, move_ids ):
518                     #if one of the related order lines is in state draft, auto or confirmed
519                     #this order line is not yet delivered
520                     if move.state in ('draft', 'auto', 'confirmed'):
521                         pending_deliveries = True
522                 # Reason => if there are no move lines,the following condition will always set to be true,and will set SO to 'DONE'.
523                 # Added move_ids check to SOLVE.
524                 if move_ids and ((not line.procurement_id) or (line.procurement_id.state=='done')) and not pending_deliveries:
525 #                    finished = True
526                     if line.state != 'done':
527                         write_done_ids.append(line.id)
528                 else:
529                     finished = False
530                 if line.procurement_id:
531                     if (line.procurement_id.state == 'cancel'):
532                         canceled = True
533                         if line.state != 'exception':
534                             write_cancel_ids.append(line.id)
535                     else:
536                         notcanceled = True
537         if write_done_ids:
538             self.pool.get('sale.order.line').write(cr, uid, write_done_ids, {'state': 'done'})
539         if write_cancel_ids:
540             self.pool.get('sale.order.line').write(cr, uid, write_cancel_ids, {'state': 'exception'})
541
542         if mode == 'finished':
543             return finished
544         elif mode == 'canceled':
545             return canceled
546             if notcanceled:
547                 return False
548             return canceled
549
550     def action_ship_create(self, cr, uid, ids, *args):
551         picking_id = False
552         company = self.pool.get('res.users').browse(cr, uid, uid).company_id
553         for order in self.browse(cr, uid, ids, context={}):
554             output_id = order.shop_id.warehouse_id.lot_output_id.id
555             picking_id = False
556             for line in order.order_line:
557                 proc_id = False
558                 date_planned = DateTime.now() + DateTime.DateTimeDeltaFromDays(line.delay or 0.0)
559                 date_planned = (date_planned - DateTime.DateTimeDeltaFromDays(company.security_lead)).strftime('%Y-%m-%d %H:%M:%S')
560                 if line.state == 'done':
561                     continue
562                 if line.product_id and line.product_id.product_tmpl_id.type in ('product', 'consu'):
563                     location_id = order.shop_id.warehouse_id.lot_stock_id.id
564                     if not picking_id:
565                         loc_dest_id = order.partner_id.property_stock_customer.id
566                         picking_id = self.pool.get('stock.picking').create(cr, uid, {
567                             'origin': order.name,
568                             'type': 'out',
569                             'state': 'auto',
570                             'move_type': order.picking_policy,
571                             'sale_id': order.id,
572                             'address_id': order.partner_shipping_id.id,
573                             'note': order.note,
574                             'invoice_state': (order.order_policy=='picking' and '2binvoiced') or 'none',
575
576                         })
577
578                     move_id = self.pool.get('stock.move').create(cr, uid, {
579                         'name': line.name[:64],
580                         'picking_id': picking_id,
581                         'product_id': line.product_id.id,
582                         'date_planned': date_planned,
583                         'product_qty': line.product_uom_qty,
584                         'product_uom': line.product_uom.id,
585                         'product_uos_qty': line.product_uos_qty,
586                         'product_uos': (line.product_uos and line.product_uos.id)\
587                                 or line.product_uom.id,
588                         'product_packaging': line.product_packaging.id,
589                         'address_id': line.address_allotment_id.id or order.partner_shipping_id.id,
590                         'location_id': location_id,
591                         'location_dest_id': output_id,
592                         'sale_line_id': line.id,
593                         'tracking_id': False,
594                         'state': 'draft',
595                         #'state': 'waiting',
596                         'note': line.notes,
597                     })
598                     proc_id = self.pool.get('mrp.procurement').create(cr, uid, {
599                         'name': order.name,
600                         'origin': order.name,
601                         'date_planned': date_planned,
602                         'product_id': line.product_id.id,
603                         'product_qty': line.product_uom_qty,
604                         'product_uom': line.product_uom.id,
605                         'product_uos_qty': (line.product_uos and line.product_uos_qty)\
606                                 or line.product_uom_qty,
607                         'product_uos': (line.product_uos and line.product_uos.id)\
608                                 or line.product_uom.id,
609                         'location_id': order.shop_id.warehouse_id.lot_stock_id.id,
610                         'procure_method': line.type,
611                         'move_id': move_id,
612                         'property_ids': [(6, 0, [x.id for x in line.property_ids])],
613                     })
614                     wf_service = netsvc.LocalService("workflow")
615                     wf_service.trg_validate(uid, 'mrp.procurement', proc_id, 'button_confirm', cr)
616                     self.pool.get('sale.order.line').write(cr, uid, [line.id], {'procurement_id': proc_id})
617                 elif line.product_id and line.product_id.product_tmpl_id.type == 'service':
618                     proc_id = self.pool.get('mrp.procurement').create(cr, uid, {
619                         'name': line.name,
620                         'origin': order.name,
621                         'date_planned': date_planned,
622                         'product_id': line.product_id.id,
623                         'product_qty': line.product_uom_qty,
624                         'product_uom': line.product_uom.id,
625                         'location_id': order.shop_id.warehouse_id.lot_stock_id.id,
626                         'procure_method': line.type,
627                         'property_ids': [(6, 0, [x.id for x in line.property_ids])],
628                     })
629                     self.pool.get('sale.order.line').write(cr, uid, [line.id], {'procurement_id': proc_id})
630                     wf_service = netsvc.LocalService("workflow")
631                     wf_service.trg_validate(uid, 'mrp.procurement', proc_id, 'button_confirm', cr)
632                 else:
633                     #
634                     # No procurement because no product in the sale.order.line.
635                     #
636                     pass
637
638             val = {}
639             if picking_id:
640                 wf_service = netsvc.LocalService("workflow")
641                 wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
642
643             if order.state == 'shipping_except':
644                 val['state'] = 'progress'
645
646                 if (order.order_policy == 'manual'):
647                     for line in order.order_line:
648                         if (not line.invoiced) and (line.state not in ('cancel', 'draft')):
649                             val['state'] = 'manual'
650                             break
651             self.write(cr, uid, [order.id], val)
652
653         return True
654
655     def action_ship_end(self, cr, uid, ids, context={}):
656         for order in self.browse(cr, uid, ids):
657             val = {'shipped': True}
658             if order.state == 'shipping_except':
659                 val['state'] = 'progress'
660                 if (order.order_policy == 'manual'):
661                     for line in order.order_line:
662                         if (not line.invoiced) and (line.state not in ('cancel', 'draft')):
663                             val['state'] = 'manual'
664                             break
665             for line in order.order_line:
666                 towrite = []
667                 if line.state == 'exception':
668                     towrite.append(line.id)
669                 if towrite:
670                     self.pool.get('sale.order.line').write(cr, uid, towrite, {'state': 'done'}, context=context)
671             self.write(cr, uid, [order.id], val)
672         return True
673
674     def _log_event(self, cr, uid, ids, factor=0.7, name='Open Order'):
675         invs = self.read(cr, uid, ids, ['date_order', 'partner_id', 'amount_untaxed'])
676         for inv in invs:
677             part = inv['partner_id'] and inv['partner_id'][0]
678             pr = inv['amount_untaxed'] or 0.0
679             partnertype = 'customer'
680             eventtype = 'sale'
681             event = {
682                 'name': 'Order: '+name,
683                 'som': False,
684                 'description': 'Order '+str(inv['id']),
685                 'document': '',
686                 'partner_id': part,
687                 'date': time.strftime('%Y-%m-%d'),
688                 'canal_id': False,
689                 'user_id': uid,
690                 'partner_type': partnertype,
691                 'probability': 1.0,
692                 'planned_revenue': pr,
693                 'planned_cost': 0.0,
694                 'type': eventtype
695             }
696             self.pool.get('res.partner.event').create(cr, uid, event)
697
698     def has_stockable_products(self, cr, uid, ids, *args):
699         for order in self.browse(cr, uid, ids):
700             for order_line in order.order_line:
701                 if order_line.product_id and order_line.product_id.product_tmpl_id.type in ('product', 'consu'):
702                     return True
703         return False
704 sale_order()
705
706 # TODO add a field price_unit_uos
707 # - update it on change product and unit price
708 # - use it in report if there is a uos
709 class sale_order_line(osv.osv):
710     def _amount_line_net(self, cr, uid, ids, field_name, arg, context):
711         res = {}
712         for line in self.browse(cr, uid, ids):
713             res[line.id] = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
714         return res
715
716     def _amount_line(self, cr, uid, ids, field_name, arg, context):
717         res = {}
718         cur_obj = self.pool.get('res.currency')
719         for line in self.browse(cr, uid, ids):
720             res[line.id] = line.price_unit * line.product_uom_qty * (1 - (line.discount or 0.0) / 100.0)
721             cur = line.order_id.pricelist_id.currency_id
722             res[line.id] = cur_obj.round(cr, uid, cur, res[line.id])
723         return res
724
725     def _number_packages(self, cr, uid, ids, field_name, arg, context):
726         res = {}
727         for line in self.browse(cr, uid, ids):
728             try:
729                 res[line.id] = int(line.product_uom_qty / line.product_packaging.qty)
730             except:
731                 res[line.id] = 1
732         return res
733
734     _name = 'sale.order.line'
735     _description = 'Sale Order line'
736     _columns = {
737         'order_id': fields.many2one('sale.order', 'Order Ref', required=True, ondelete='cascade', select=True),
738         'name': fields.char('Description', size=256, required=True, select=True),
739         'sequence': fields.integer('Sequence'),
740         'delay': fields.float('Delivery Lead Time', required=True, help="Number of days between the order confirmation the the shipping of the products to the customer"),
741         'product_id': fields.many2one('product.product', 'Product', domain=[('sale_ok', '=', True)], change_default=True),
742         'invoice_lines': fields.many2many('account.invoice.line', 'sale_order_line_invoice_rel', 'order_line_id', 'invoice_id', 'Invoice Lines', readonly=True),
743         'invoiced': fields.boolean('Invoiced', readonly=True),
744         'procurement_id': fields.many2one('mrp.procurement', 'Procurement'),
745         'price_unit': fields.float('Unit Price', required=True, digits=(16, int(config['price_accuracy']))),
746         'price_net': fields.function(_amount_line_net, method=True, string='Net Price', digits=(16, int(config['price_accuracy']))),
747         'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal'),
748         'tax_id': fields.many2many('account.tax', 'sale_order_tax', 'order_line_id', 'tax_id', 'Taxes'),
749         'type': fields.selection([('make_to_stock', 'from stock'), ('make_to_order', 'on order')], 'Procure Method', required=True),
750         'property_ids': fields.many2many('mrp.property', 'sale_order_line_property_rel', 'order_id', 'property_id', 'Properties'),
751         'address_allotment_id': fields.many2one('res.partner.address', 'Allotment Partner'),
752         'product_uom_qty': fields.float('Quantity (UoM)', digits=(16, 2), required=True),
753         'product_uom': fields.many2one('product.uom', 'Product UoM', required=True),
754         'product_uos_qty': fields.float('Quantity (UoS)'),
755         'product_uos': fields.many2one('product.uom', 'Product UoS'),
756         'product_packaging': fields.many2one('product.packaging', 'Packaging'),
757         'move_ids': fields.one2many('stock.move', 'sale_line_id', 'Inventory Moves', readonly=True),
758         'discount': fields.float('Discount (%)', digits=(16, 2)),
759         'number_packages': fields.function(_number_packages, method=True, type='integer', string='Number Packages'),
760         'notes': fields.text('Notes'),
761         'th_weight': fields.float('Weight'),
762         'state': fields.selection([('draft', 'Draft'), ('confirmed', 'Confirmed'), ('done', 'Done'), ('cancel', 'Cancelled'), ('exception', 'Exception')], 'Status', required=True, readonly=True),
763         'order_partner_id': fields.related('order_id', 'partner_id', type='many2one', relation='res.partner', string='Customer')
764     }
765     _order = 'sequence, id'
766     _defaults = {
767         'discount': lambda *a: 0.0,
768         'delay': lambda *a: 0.0,
769         'product_uom_qty': lambda *a: 1,
770         'product_uos_qty': lambda *a: 1,
771         'sequence': lambda *a: 10,
772         'invoiced': lambda *a: 0,
773         'state': lambda *a: 'draft',
774         'type': lambda *a: 'make_to_stock',
775         'product_packaging': lambda *a: False
776     }
777
778     def invoice_line_create(self, cr, uid, ids, context={}):
779         def _get_line_qty(line):
780             if (line.order_id.invoice_quantity=='order') or not line.procurement_id:
781                 if line.product_uos:
782                     return line.product_uos_qty or 0.0
783                 return line.product_uom_qty
784             else:
785                 return self.pool.get('mrp.procurement').quantity_get(cr, uid,
786                         line.procurement_id.id, context)
787
788         def _get_line_uom(line):
789             if (line.order_id.invoice_quantity=='order') or not line.procurement_id:
790                 if line.product_uos:
791                     return line.product_uos.id
792                 return line.product_uom.id
793             else:
794                 return self.pool.get('mrp.procurement').uom_get(cr, uid,
795                         line.procurement_id.id, context)
796
797         create_ids = []
798         sales = {}
799         for line in self.browse(cr, uid, ids, context):
800             if not line.invoiced:
801                 if line.product_id:
802                     a = line.product_id.product_tmpl_id.property_account_income.id
803                     if not a:
804                         a = line.product_id.categ_id.property_account_income_categ.id
805                     if not a:
806                         raise osv.except_osv(_('Error !'),
807                                 _('There is no income account defined ' \
808                                         'for this product: "%s" (id:%d)') % \
809                                         (line.product_id.name, line.product_id.id,))
810                 else:
811                     a = self.pool.get('ir.property').get(cr, uid,
812                             'property_account_income_categ', 'product.category',
813                             context=context)
814                 uosqty = _get_line_qty(line)
815                 uos_id = _get_line_uom(line)
816                 pu = 0.0
817                 if uosqty:
818                     pu = round(line.price_unit * line.product_uom_qty / uosqty,
819                             int(config['price_accuracy']))
820                 fpos = line.order_id.fiscal_position or False
821                 a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
822                 if not a:
823                     raise osv.except_osv(_('Error !'),
824                                 _('There is no income category account defined in default Properties for Product Category or Fiscal Position is not defined !'))
825                 inv_id = self.pool.get('account.invoice.line').create(cr, uid, {
826                     'name': line.name,
827                     'origin': line.order_id.name,
828                     'account_id': a,
829                     'price_unit': pu,
830                     'quantity': uosqty,
831                     'discount': line.discount,
832                     'uos_id': uos_id,
833                     'product_id': line.product_id.id or False,
834                     'invoice_line_tax_id': [(6, 0, [x.id for x in line.tax_id])],
835                     'note': line.notes,
836                     'account_analytic_id': line.order_id.project_id and line.order_id.project_id.id or False,
837                 })
838                 cr.execute('insert into sale_order_line_invoice_rel (order_line_id,invoice_id) values (%s,%s)', (line.id, inv_id))
839                 self.write(cr, uid, [line.id], {'invoiced': True})
840
841                 sales[line.order_id.id] = True
842                 create_ids.append(inv_id)
843
844         # Trigger workflow events
845         wf_service = netsvc.LocalService("workflow")
846         for sid in sales.keys():
847             wf_service.trg_write(uid, 'sale.order', sid, cr)
848         return create_ids
849
850     def button_cancel(self, cr, uid, ids, context={}):
851         for line in self.browse(cr, uid, ids, context=context):
852             if line.invoiced:
853                 raise osv.except_osv(_('Invalid action !'), _('You cannot cancel a sale order line that has already been invoiced !'))
854         return self.write(cr, uid, ids, {'state': 'cancel'})
855
856     def button_confirm(self, cr, uid, ids, context={}):
857         return self.write(cr, uid, ids, {'state': 'confirmed'})
858
859     def button_done(self, cr, uid, ids, context={}):
860         wf_service = netsvc.LocalService("workflow")
861         res = self.write(cr, uid, ids, {'state': 'done'})
862         for line in self.browse(cr, uid, ids, context):
863             wf_service.trg_write(uid, 'sale.order', line.order_id.id, cr)
864
865         return res
866
867     def uos_change(self, cr, uid, ids, product_uos, product_uos_qty=0, product_id=None):
868         product_obj = self.pool.get('product.product')
869         if not product_id:
870             return {'value': {'product_uom': product_uos,
871                 'product_uom_qty': product_uos_qty}, 'domain': {}}
872
873         product = product_obj.browse(cr, uid, product_id)
874         value = {
875             'product_uom': product.uom_id.id,
876         }
877         # FIXME must depend on uos/uom of the product and not only of the coeff.
878         try:
879             value.update({
880                 'product_uom_qty': product_uos_qty / product.uos_coeff,
881                 'th_weight': product_uos_qty / product.uos_coeff * product.weight
882             })
883         except ZeroDivisionError:
884             pass
885         return {'value': value}
886
887     def copy_data(self, cr, uid, id, default=None, context={}):
888         if not default:
889             default = {}
890         default.update({'state': 'draft', 'move_ids': [], 'invoiced': False, 'invoice_lines': []})
891         return super(sale_order_line, self).copy_data(cr, uid, id, default, context)
892
893     def product_id_change(self, cr, uid, ids, pricelist, product, qty=0,
894             uom=False, qty_uos=0, uos=False, name='', partner_id=False,
895             lang=False, update_tax=True, date_order=False, packaging=False, fiscal_position=False, flag=False):
896         if not  partner_id:
897             raise osv.except_osv(_('No Customer Defined !'), _('You have to select a customer in the sale form !\nPlease set one customer before choosing a product.'))
898         warning = {}
899         product_uom_obj = self.pool.get('product.uom')
900         partner_obj = self.pool.get('res.partner')
901         product_obj = self.pool.get('product.product')
902         if partner_id:
903             lang = partner_obj.browse(cr, uid, partner_id).lang
904         context = {'lang': lang, 'partner_id': partner_id}
905
906         if not product:
907             return {'value': {'th_weight': 0, 'product_packaging': False,
908                 'product_uos_qty': qty}, 'domain': {'product_uom': [],
909                    'product_uos': []}}
910
911         if not date_order:
912             date_order = time.strftime('%Y-%m-%d')
913
914         result = {}
915         product_obj = product_obj.browse(cr, uid, product, context=context)
916         if not packaging and product_obj.packaging:
917             packaging = product_obj.packaging[0].id
918             result['product_packaging'] = packaging
919
920         if packaging:
921             default_uom = product_obj.uom_id and product_obj.uom_id.id
922             pack = self.pool.get('product.packaging').browse(cr, uid, packaging, context)
923             q = product_uom_obj._compute_qty(cr, uid, uom, pack.qty, default_uom)
924 #            qty = qty - qty % q + q
925             if qty and (q and not (qty % q) == 0):
926                 ean = pack.ean
927                 qty_pack = pack.qty
928                 type_ul = pack.ul
929                 warn_msg = _("You selected a quantity of %d Units.\nBut it's not compatible with the selected packaging.\nHere is a proposition of quantities according to the packaging: ") % (qty)
930                 warn_msg = warn_msg + "\n\n" + _("EAN: ") + str(ean) + _(" Quantity: ") + str(qty_pack) + _(" Type of ul: ") + str(type_ul.name)
931                 warning = {
932                     'title': _('Packing Information !'),
933                     'message': warn_msg
934                     }
935             result['product_uom_qty'] = qty
936
937         if uom:
938             uom2 = product_uom_obj.browse(cr, uid, uom)
939             if product_obj.uom_id.category_id.id != uom2.category_id.id:
940                 uom = False
941
942         if uos:
943             if product_obj.uos_id:
944                 uos2 = product_uom_obj.browse(cr, uid, uos)
945                 if product_obj.uos_id.category_id.id != uos2.category_id.id:
946                     uos = False
947             else:
948                 uos = False
949         result.update({'type': product_obj.procure_method})
950         if product_obj.description_sale:
951             result['notes'] = product_obj.description_sale
952         fpos = fiscal_position and self.pool.get('account.fiscal.position').browse(cr, uid, fiscal_position) or False
953         if update_tax: #The quantity only have changed
954             result['delay'] = (product_obj.sale_delay or 0.0)
955             partner = partner_obj.browse(cr, uid, partner_id)
956             result['tax_id'] = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, product_obj.taxes_id)
957         if not flag:
958             result['name'] = product_obj.partner_ref
959         domain = {}
960         if (not uom) and (not uos):
961             result['product_uom'] = product_obj.uom_id.id
962             if product_obj.uos_id:
963                 result['product_uos'] = product_obj.uos_id.id
964                 result['product_uos_qty'] = qty * product_obj.uos_coeff
965                 uos_category_id = product_obj.uos_id.category_id.id
966             else:
967                 result['product_uos'] = False
968                 result['product_uos_qty'] = qty
969                 uos_category_id = False
970             result['th_weight'] = qty * product_obj.weight
971             domain = {'product_uom':
972                         [('category_id', '=', product_obj.uom_id.category_id.id)],
973                         'product_uos':
974                         [('category_id', '=', uos_category_id)]}
975
976         elif uos: # only happens if uom is False
977             result['product_uom'] = product_obj.uom_id and product_obj.uom_id.id
978             result['product_uom_qty'] = qty_uos / product_obj.uos_coeff
979             result['th_weight'] = result['product_uom_qty'] * product_obj.weight
980         elif uom: # whether uos is set or not
981             default_uom = product_obj.uom_id and product_obj.uom_id.id
982             q = product_uom_obj._compute_qty(cr, uid, uom, qty, default_uom)
983             if product_obj.uos_id:
984                 result['product_uos'] = product_obj.uos_id.id
985                 result['product_uos_qty'] = qty * product_obj.uos_coeff
986             else:
987                 result['product_uos'] = False
988                 result['product_uos_qty'] = qty
989             result['th_weight'] = q * product_obj.weight        # Round the quantity up
990
991         # get unit price
992
993         if not pricelist:
994             warning = {
995                 'title': 'No Pricelist !',
996                 'message':
997                     'You have to select a pricelist in the sale form !\n'
998                     'Please set one before choosing a product.'
999                 }
1000         else:
1001             price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist],
1002                     product, qty or 1.0, partner_id, {
1003                         'uom': uom,
1004                         'date': date_order,
1005                         })[pricelist]
1006             if price is False:
1007                 warning = {
1008                     'title': 'No valid pricelist line found !',
1009                     'message':
1010                         "Couldn't find a pricelist line matching this product and quantity.\n"
1011                         "You have to change either the product, the quantity or the pricelist."
1012                     }
1013             else:
1014                 result.update({'price_unit': price})
1015         return {'value': result, 'domain': domain, 'warning': warning}
1016
1017     def product_uom_change(self, cursor, user, ids, pricelist, product, qty=0,
1018             uom=False, qty_uos=0, uos=False, name='', partner_id=False,
1019             lang=False, update_tax=True, date_order=False):
1020         res = self.product_id_change(cursor, user, ids, pricelist, product,
1021                 qty=0, uom=uom, qty_uos=qty_uos, uos=uos, name=name,
1022                 partner_id=partner_id, lang=lang, update_tax=update_tax,
1023                 date_order=date_order)
1024         if 'product_uom' in res['value']:
1025             del res['value']['product_uom']
1026         if not uom:
1027             res['value']['price_unit'] = 0.0
1028         return res
1029
1030     def unlink(self, cr, uid, ids, context={}):
1031         """Allows to delete sale order lines in draft,cancel states"""
1032         for rec in self.browse(cr, uid, ids, context=context):
1033             if rec.state not in ['draft', 'cancel']:
1034                 raise osv.except_osv(_('Invalid action !'), _('Cannot delete a sale order line which is %s !')%(rec.state,))
1035         return super(sale_order_line, self).unlink(cr, uid, ids, context=context)
1036
1037 sale_order_line()
1038
1039
1040 class sale_config_picking_policy(osv.osv_memory):
1041     _name = 'sale.config.picking_policy'
1042     _columns = {
1043         'name': fields.char('Name', size=64),
1044         'picking_policy': fields.selection([
1045             ('direct', 'Direct Delivery'),
1046             ('one', 'All at Once')
1047         ], 'Packing Default Policy', required=True),
1048         'order_policy': fields.selection([
1049             ('manual', 'Invoice Based on Sales Orders'),
1050             ('picking', 'Invoice Based on Deliveries'),
1051         ], 'Shipping Default Policy', required=True),
1052         'step': fields.selection([
1053             ('one', 'Delivery Order Only'),
1054             ('two', 'Packing List & Delivery Order')
1055         ], 'Steps To Deliver a Sale Order', required=True,
1056            help="By default, Open ERP is able to manage complex routing and paths "\
1057            "of products in your warehouse and partner locations. This will configure "\
1058            "the most common and simple methods to deliver products to the customer "\
1059            "in one or two operations by the worker.")
1060     }
1061     _defaults = {
1062         'picking_policy': lambda *a: 'direct',
1063         'order_policy': lambda *a: 'picking',
1064         'step': lambda *a: 'one'
1065     }
1066
1067     def set_default(self, cr, uid, ids, context=None):
1068         for o in self.browse(cr, uid, ids, context=context):
1069             ir_values_obj = self.pool.get('ir.values')
1070             ir_values_obj.set(cr, uid, 'default', False, 'picking_policy', ['sale.order'], o.picking_policy)
1071             ir_values_obj.set(cr, uid, 'default', False, 'order_policy', ['sale.order'], o.order_policy)
1072
1073             if o.step == 'one':
1074                 md = self.pool.get('ir.model.data')
1075                 group_id = md._get_id(cr, uid, 'base', 'group_no_one')
1076                 group_id = md.browse(cr, uid, group_id, context).res_id
1077                 menu_id = md._get_id(cr, uid, 'stock', 'menu_action_picking_tree_delivery')
1078                 menu_id = md.browse(cr, uid, menu_id, context).res_id
1079                 self.pool.get('ir.ui.menu').write(cr, uid, [menu_id], {'groups_id': [(6, 0, [group_id])]})
1080
1081                 location_id = md._get_id(cr, uid, 'stock', 'stock_location_output')
1082                 location_id = md.browse(cr, uid, location_id, context).res_id
1083                 self.pool.get('stock.location').write(cr, uid, [location_id], {'chained_auto_packing': 'transparent'})
1084
1085         return {
1086                 'view_type': 'form',
1087                 "view_mode": 'form',
1088                 'res_model': 'ir.actions.configuration.wizard',
1089                 'type': 'ir.actions.act_window',
1090                 'target': 'new',
1091          }
1092
1093     def action_cancel(self, cr, uid, ids, context=None):
1094         return {
1095                 'view_type': 'form',
1096                 "view_mode": 'form',
1097                 'res_model': 'ir.actions.configuration.wizard',
1098                 'type': 'ir.actions.act_window',
1099                 'target': 'new',
1100          }
1101
1102 sale_config_picking_policy()
1103