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