[FIX] Sale: Passing Contact address for invoices from SO
[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', 'Invoice', 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, 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, 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, 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
509         for order in self.browse(cr, uid, ids, context={}):
510             
511             #check for pending deliveries 
512             pending_deliveries = False
513             
514             for line in order.order_line:    
515                 move_ids = stock_move_obj.search(cr, uid, [('sale_line_id','=', line.id)])
516                 for move in stock_move_obj.browse( cr, uid, move_ids ):
517                     #if one of the related order lines is in state draft, auto or confirmed
518                     #this order line is not yet delivered
519                     if move.state in ('draft', 'auto', 'confirmed'):
520                         pending_deliveries = True
521                 
522                 if ((not line.procurement_id) or (line.procurement_id.state=='done')) and not pending_deliveries:
523 #                    finished = True
524                     if line.state != 'done':
525                         write_done_ids.append(line.id)
526                 else:
527                     finished = False
528                 if line.procurement_id:
529                     if (line.procurement_id.state == 'cancel'):
530                         canceled = True
531                         if line.state != 'exception':
532                             write_cancel_ids.append(line.id)
533                     else:
534                         notcanceled = True
535
536         if write_done_ids:
537             self.pool.get('sale.order.line').write(cr, uid, write_done_ids, {'state': 'done'})
538         if write_cancel_ids:
539             self.pool.get('sale.order.line').write(cr, uid, write_cancel_ids, {'state': 'exception'})
540
541         if mode == 'finished':
542             return finished
543         elif mode == 'canceled':
544             return canceled
545             if notcanceled:
546                 return False
547             return canceled
548
549     def action_ship_create(self, cr, uid, ids, *args):
550         picking_id = False
551         company = self.pool.get('res.users').browse(cr, uid, uid).company_id
552         for order in self.browse(cr, uid, ids, context={}):
553             output_id = order.shop_id.warehouse_id.lot_output_id.id
554             picking_id = False
555             for line in order.order_line:
556                 proc_id = False
557                 date_planned = DateTime.now() + DateTime.DateTimeDeltaFromDays(line.delay or 0.0)
558                 date_planned = (date_planned - DateTime.DateTimeDeltaFromDays(company.security_lead)).strftime('%Y-%m-%d %H:%M:%S')
559                 if line.state == 'done':
560                     continue
561                 if line.product_id and line.product_id.product_tmpl_id.type in ('product', 'consu'):
562                     location_id = order.shop_id.warehouse_id.lot_stock_id.id
563                     if not picking_id:
564                         loc_dest_id = order.partner_id.property_stock_customer.id
565                         picking_id = self.pool.get('stock.picking').create(cr, uid, {
566                             'origin': order.name,
567                             'type': 'out',
568                             'state': 'auto',
569                             'move_type': order.picking_policy,
570                             'sale_id': order.id,
571                             'address_id': order.partner_shipping_id.id,
572                             'note': order.note,
573                             'invoice_state': (order.order_policy=='picking' and '2binvoiced') or 'none',
574
575                         })
576
577                     move_id = self.pool.get('stock.move').create(cr, uid, {
578                         'name': line.name[:64],
579                         'picking_id': picking_id,
580                         'product_id': line.product_id.id,
581                         'date_planned': date_planned,
582                         'product_qty': line.product_uom_qty,
583                         'product_uom': line.product_uom.id,
584                         'product_uos_qty': line.product_uos_qty,
585                         'product_uos': (line.product_uos and line.product_uos.id)\
586                                 or line.product_uom.id,
587                         'product_packaging': line.product_packaging.id,
588                         'address_id': line.address_allotment_id.id or order.partner_shipping_id.id,
589                         'location_id': location_id,
590                         'location_dest_id': output_id,
591                         'sale_line_id': line.id,
592                         'tracking_id': False,
593                         'state': 'draft',
594                         #'state': 'waiting',
595                         'note': line.notes,
596                     })
597                     proc_id = self.pool.get('mrp.procurement').create(cr, uid, {
598                         'name': order.name,
599                         'origin': order.name,
600                         'date_planned': date_planned,
601                         'product_id': line.product_id.id,
602                         'product_qty': line.product_uom_qty,
603                         'product_uom': line.product_uom.id,
604                         'product_uos_qty': (line.product_uos and line.product_uos_qty)\
605                                 or line.product_uom_qty,
606                         'product_uos': (line.product_uos and line.product_uos.id)\
607                                 or line.product_uom.id,
608                         'location_id': order.shop_id.warehouse_id.lot_stock_id.id,
609                         'procure_method': line.type,
610                         'move_id': move_id,
611                         'property_ids': [(6, 0, [x.id for x in line.property_ids])],
612                     })
613                     wf_service = netsvc.LocalService("workflow")
614                     wf_service.trg_validate(uid, 'mrp.procurement', proc_id, 'button_confirm', cr)
615                     self.pool.get('sale.order.line').write(cr, uid, [line.id], {'procurement_id': proc_id})
616                 elif line.product_id and line.product_id.product_tmpl_id.type == 'service':
617                     proc_id = self.pool.get('mrp.procurement').create(cr, uid, {
618                         'name': line.name,
619                         'origin': order.name,
620                         'date_planned': date_planned,
621                         'product_id': line.product_id.id,
622                         'product_qty': line.product_uom_qty,
623                         'product_uom': line.product_uom.id,
624                         'location_id': order.shop_id.warehouse_id.lot_stock_id.id,
625                         'procure_method': line.type,
626                         'property_ids': [(6, 0, [x.id for x in line.property_ids])],
627                     })
628                     self.pool.get('sale.order.line').write(cr, uid, [line.id], {'procurement_id': proc_id})
629                     wf_service = netsvc.LocalService("workflow")
630                     wf_service.trg_validate(uid, 'mrp.procurement', proc_id, 'button_confirm', cr)
631                 else:
632                     #
633                     # No procurement because no product in the sale.order.line.
634                     #
635                     pass
636
637             val = {}
638             if picking_id:
639                 wf_service = netsvc.LocalService("workflow")
640                 wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
641
642             if order.state == 'shipping_except':
643                 val['state'] = 'progress'
644
645                 if (order.order_policy == 'manual'):
646                     for line in order.order_line:
647                         if (not line.invoiced) and (line.state not in ('cancel', 'draft')):
648                             val['state'] = 'manual'
649                             break
650             self.write(cr, uid, [order.id], val)
651
652         return True
653
654     def action_ship_end(self, cr, uid, ids, context={}):
655         for order in self.browse(cr, uid, ids):
656             val = {'shipped': True}
657             if order.state == 'shipping_except':
658                 val['state'] = 'progress'
659                 if (order.order_policy == 'manual'):
660                     for line in order.order_line:
661                         if (not line.invoiced) and (line.state not in ('cancel', 'draft')):
662                             val['state'] = 'manual'
663                             break
664             for line in order.order_line:
665                 towrite = []
666                 if line.state == 'exception':
667                     towrite.append(line.id)
668                 if towrite:
669                     self.pool.get('sale.order.line').write(cr, uid, towrite, {'state': 'done'}, context=context)
670             self.write(cr, uid, [order.id], val)
671         return True
672
673     def _log_event(self, cr, uid, ids, factor=0.7, name='Open Order'):
674         invs = self.read(cr, uid, ids, ['date_order', 'partner_id', 'amount_untaxed'])
675         for inv in invs:
676             part = inv['partner_id'] and inv['partner_id'][0]
677             pr = inv['amount_untaxed'] or 0.0
678             partnertype = 'customer'
679             eventtype = 'sale'
680             event = {
681                 'name': 'Order: '+name,
682                 'som': False,
683                 'description': 'Order '+str(inv['id']),
684                 'document': '',
685                 'partner_id': part,
686                 'date': time.strftime('%Y-%m-%d'),
687                 'canal_id': False,
688                 'user_id': uid,
689                 'partner_type': partnertype,
690                 'probability': 1.0,
691                 'planned_revenue': pr,
692                 'planned_cost': 0.0,
693                 'type': eventtype
694             }
695             self.pool.get('res.partner.event').create(cr, uid, event)
696
697     def has_stockable_products(self, cr, uid, ids, *args):
698         for order in self.browse(cr, uid, ids):
699             for order_line in order.order_line:
700                 if order_line.product_id and order_line.product_id.product_tmpl_id.type in ('product', 'consu'):
701                     return True
702         return False
703 sale_order()
704
705 # TODO add a field price_unit_uos
706 # - update it on change product and unit price
707 # - use it in report if there is a uos
708 class sale_order_line(osv.osv):
709     def _amount_line_net(self, cr, uid, ids, field_name, arg, context):
710         res = {}
711         for line in self.browse(cr, uid, ids):
712             res[line.id] = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
713         return res
714
715     def _amount_line(self, cr, uid, ids, field_name, arg, context):
716         res = {}
717         cur_obj = self.pool.get('res.currency')
718         for line in self.browse(cr, uid, ids):
719             res[line.id] = line.price_unit * line.product_uom_qty * (1 - (line.discount or 0.0) / 100.0)
720             cur = line.order_id.pricelist_id.currency_id
721             res[line.id] = cur_obj.round(cr, uid, cur, res[line.id])
722         return res
723
724     def _number_packages(self, cr, uid, ids, field_name, arg, context):
725         res = {}
726         for line in self.browse(cr, uid, ids):
727             try:
728                 res[line.id] = int(line.product_uom_qty / line.product_packaging.qty)
729             except:
730                 res[line.id] = 1
731         return res
732
733     _name = 'sale.order.line'
734     _description = 'Sale Order line'
735     _columns = {
736         'order_id': fields.many2one('sale.order', 'Order Ref', required=True, ondelete='cascade', select=True),
737         'name': fields.char('Description', size=256, required=True, select=True),
738         'sequence': fields.integer('Sequence'),
739         'delay': fields.float('Delivery Delay', required=True),
740         'product_id': fields.many2one('product.product', 'Product', domain=[('sale_ok', '=', True)], change_default=True),
741         'invoice_lines': fields.many2many('account.invoice.line', 'sale_order_line_invoice_rel', 'order_line_id', 'invoice_id', 'Invoice Lines', readonly=True),
742         'invoiced': fields.boolean('Invoiced', readonly=True),
743         'procurement_id': fields.many2one('mrp.procurement', 'Procurement'),
744         'price_unit': fields.float('Unit Price', required=True, digits=(16, int(config['price_accuracy']))),
745         'price_net': fields.function(_amount_line_net, method=True, string='Net Price', digits=(16, int(config['price_accuracy']))),
746         'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal'),
747         'tax_id': fields.many2many('account.tax', 'sale_order_tax', 'order_line_id', 'tax_id', 'Taxes'),
748         'type': fields.selection([('make_to_stock', 'from stock'), ('make_to_order', 'on order')], 'Procure Method', required=True),
749         'property_ids': fields.many2many('mrp.property', 'sale_order_line_property_rel', 'order_id', 'property_id', 'Properties'),
750         'address_allotment_id': fields.many2one('res.partner.address', 'Allotment Partner'),
751         'product_uom_qty': fields.float('Quantity (UoM)', digits=(16, 2), required=True),
752         'product_uom': fields.many2one('product.uom', 'Product UoM', required=True),
753         'product_uos_qty': fields.float('Quantity (UoS)'),
754         'product_uos': fields.many2one('product.uom', 'Product UoS'),
755         'product_packaging': fields.many2one('product.packaging', 'Packaging'),
756         'move_ids': fields.one2many('stock.move', 'sale_line_id', 'Inventory Moves', readonly=True),
757         'discount': fields.float('Discount (%)', digits=(16, 2)),
758         'number_packages': fields.function(_number_packages, method=True, type='integer', string='Number Packages'),
759         'notes': fields.text('Notes'),
760         'th_weight': fields.float('Weight'),
761         'state': fields.selection([('draft', 'Draft'), ('confirmed', 'Confirmed'), ('done', 'Done'), ('cancel', 'Cancelled'), ('exception', 'Exception')], 'Status', required=True, readonly=True),
762         'order_partner_id': fields.related('order_id', 'partner_id', type='many2one', relation='res.partner', string='Customer')
763     }
764     _order = 'sequence, id'
765     _defaults = {
766         'discount': lambda *a: 0.0,
767         'delay': lambda *a: 0.0,
768         'product_uom_qty': lambda *a: 1,
769         'product_uos_qty': lambda *a: 1,
770         'sequence': lambda *a: 10,
771         'invoiced': lambda *a: 0,
772         'state': lambda *a: 'draft',
773         'type': lambda *a: 'make_to_stock',
774         'product_packaging': lambda *a: False
775     }
776
777     def invoice_line_create(self, cr, uid, ids, context={}):
778         def _get_line_qty(line):
779             if (line.order_id.invoice_quantity=='order') or not line.procurement_id:
780                 if line.product_uos:
781                     return line.product_uos_qty or 0.0
782                 return line.product_uom_qty
783             else:
784                 return self.pool.get('mrp.procurement').quantity_get(cr, uid,
785                         line.procurement_id.id, context)
786
787         def _get_line_uom(line):
788             if (line.order_id.invoice_quantity=='order') or not line.procurement_id:
789                 if line.product_uos:
790                     return line.product_uos.id
791                 return line.product_uom.id
792             else:
793                 return self.pool.get('mrp.procurement').uom_get(cr, uid,
794                         line.procurement_id.id, context)
795
796         create_ids = []
797         sales = {}
798         for line in self.browse(cr, uid, ids, context):
799             if not line.invoiced:
800                 if line.product_id:
801                     a = line.product_id.product_tmpl_id.property_account_income.id
802                     if not a:
803                         a = line.product_id.categ_id.property_account_income_categ.id
804                     if not a:
805                         raise osv.except_osv(_('Error !'),
806                                 _('There is no income account defined ' \
807                                         'for this product: "%s" (id:%d)') % \
808                                         (line.product_id.name, line.product_id.id,))
809                 else:
810                     a = self.pool.get('ir.property').get(cr, uid,
811                             'property_account_income_categ', 'product.category',
812                             context=context)
813                 uosqty = _get_line_qty(line)
814                 uos_id = _get_line_uom(line)
815                 pu = 0.0
816                 if uosqty:
817                     pu = round(line.price_unit * line.product_uom_qty / uosqty,
818                             int(config['price_accuracy']))
819                 fpos = line.order_id.fiscal_position or False
820                 a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
821                 inv_id = self.pool.get('account.invoice.line').create(cr, uid, {
822                     'name': line.name,
823                     'origin': line.order_id.name,
824                     'account_id': a,
825                     'price_unit': pu,
826                     'quantity': uosqty,
827                     'discount': line.discount,
828                     'uos_id': uos_id,
829                     'product_id': line.product_id.id or False,
830                     'invoice_line_tax_id': [(6, 0, [x.id for x in line.tax_id])],
831                     'note': line.notes,
832                     'account_analytic_id': line.order_id.project_id and line.order_id.project_id.id or False,
833                 })
834                 cr.execute('insert into sale_order_line_invoice_rel (order_line_id,invoice_id) values (%s,%s)', (line.id, inv_id))
835                 self.write(cr, uid, [line.id], {'invoiced': True})
836
837                 sales[line.order_id.id] = True
838                 create_ids.append(inv_id)
839
840         # Trigger workflow events
841         wf_service = netsvc.LocalService("workflow")
842         for sid in sales.keys():
843             wf_service.trg_write(uid, 'sale.order', sid, cr)
844         return create_ids
845
846     def button_cancel(self, cr, uid, ids, context={}):
847         for line in self.browse(cr, uid, ids, context=context):
848             if line.invoiced:
849                 raise osv.except_osv(_('Invalid action !'), _('You cannot cancel a sale order line that has already been invoiced !'))
850         return self.write(cr, uid, ids, {'state': 'cancel'})
851
852     def button_confirm(self, cr, uid, ids, context={}):
853         return self.write(cr, uid, ids, {'state': 'confirmed'})
854
855     def button_done(self, cr, uid, ids, context={}):
856         wf_service = netsvc.LocalService("workflow")
857         res = self.write(cr, uid, ids, {'state': 'done'})
858         for line in self.browse(cr, uid, ids, context):
859             wf_service.trg_write(uid, 'sale.order', line.order_id.id, cr)
860
861         return res
862
863     def uos_change(self, cr, uid, ids, product_uos, product_uos_qty=0, product_id=None):
864         product_obj = self.pool.get('product.product')
865         if not product_id:
866             return {'value': {'product_uom': product_uos,
867                 'product_uom_qty': product_uos_qty}, 'domain': {}}
868
869         product = product_obj.browse(cr, uid, product_id)
870         value = {
871             'product_uom': product.uom_id.id,
872         }
873         # FIXME must depend on uos/uom of the product and not only of the coeff.
874         try:
875             value.update({
876                 'product_uom_qty': product_uos_qty / product.uos_coeff,
877                 'th_weight': product_uos_qty / product.uos_coeff * product.weight
878             })
879         except ZeroDivisionError:
880             pass
881         return {'value': value}
882
883     def copy_data(self, cr, uid, id, default=None, context={}):
884         if not default:
885             default = {}
886         default.update({'state': 'draft', 'move_ids': [], 'invoiced': False, 'invoice_lines': []})
887         return super(sale_order_line, self).copy_data(cr, uid, id, default, context)
888
889     def product_id_change(self, cr, uid, ids, pricelist, product, qty=0,
890             uom=False, qty_uos=0, uos=False, name='', partner_id=False,
891             lang=False, update_tax=True, date_order=False, packaging=False, fiscal_position=False, flag=False):
892         if not  partner_id:
893             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.'))
894         warning = {}
895         product_uom_obj = self.pool.get('product.uom')
896         partner_obj = self.pool.get('res.partner')
897         product_obj = self.pool.get('product.product')
898         if partner_id:
899             lang = partner_obj.browse(cr, uid, partner_id).lang
900         context = {'lang': lang, 'partner_id': partner_id}
901
902         if not product:
903             return {'value': {'th_weight': 0, 'product_packaging': False,
904                 'product_uos_qty': qty}, 'domain': {'product_uom': [],
905                    'product_uos': []}}
906
907         if not date_order:
908             date_order = time.strftime('%Y-%m-%d')
909
910         result = {}
911         product_obj = product_obj.browse(cr, uid, product, context=context)
912         if not packaging and product_obj.packaging:
913             packaging = product_obj.packaging[0].id
914             result['product_packaging'] = packaging
915
916         if packaging:
917             default_uom = product_obj.uom_id and product_obj.uom_id.id
918             pack = self.pool.get('product.packaging').browse(cr, uid, packaging, context)
919             q = product_uom_obj._compute_qty(cr, uid, uom, pack.qty, default_uom)
920 #            qty = qty - qty % q + q
921             if qty and (q and not (qty % q) == 0):
922                 ean = pack.ean
923                 qty_pack = pack.qty
924                 type_ul = pack.ul
925                 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)
926                 warn_msg = warn_msg + "\n\n" + _("EAN: ") + str(ean) + _(" Quantity: ") + str(qty_pack) + _(" Type of ul: ") + str(type_ul.name)
927                 warning = {
928                     'title': _('Packing Information !'),
929                     'message': warn_msg
930                     }
931             result['product_uom_qty'] = qty
932
933         if uom:
934             uom2 = product_uom_obj.browse(cr, uid, uom)
935             if product_obj.uom_id.category_id.id != uom2.category_id.id:
936                 uom = False
937
938         if uos:
939             if product_obj.uos_id:
940                 uos2 = product_uom_obj.browse(cr, uid, uos)
941                 if product_obj.uos_id.category_id.id != uos2.category_id.id:
942                     uos = False
943             else:
944                 uos = False
945         result.update({'type': product_obj.procure_method})
946         if product_obj.description_sale:
947             result['notes'] = product_obj.description_sale
948         fpos = fiscal_position and self.pool.get('account.fiscal.position').browse(cr, uid, fiscal_position) or False
949         if update_tax: #The quantity only have changed
950             result['delay'] = (product_obj.sale_delay or 0.0)
951             partner = partner_obj.browse(cr, uid, partner_id)
952             result['tax_id'] = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, product_obj.taxes_id)
953         if not flag:
954             result['name'] = product_obj.partner_ref
955         domain = {}
956         if (not uom) and (not uos):
957             result['product_uom'] = product_obj.uom_id.id
958             if product_obj.uos_id:
959                 result['product_uos'] = product_obj.uos_id.id
960                 result['product_uos_qty'] = qty * product_obj.uos_coeff
961                 uos_category_id = product_obj.uos_id.category_id.id
962             else:
963                 result['product_uos'] = False
964                 result['product_uos_qty'] = qty
965                 uos_category_id = False
966             result['th_weight'] = qty * product_obj.weight
967             domain = {'product_uom':
968                         [('category_id', '=', product_obj.uom_id.category_id.id)],
969                         'product_uos':
970                         [('category_id', '=', uos_category_id)]}
971
972         elif uos: # only happens if uom is False
973             result['product_uom'] = product_obj.uom_id and product_obj.uom_id.id
974             result['product_uom_qty'] = qty_uos / product_obj.uos_coeff
975             result['th_weight'] = result['product_uom_qty'] * product_obj.weight
976         elif uom: # whether uos is set or not
977             default_uom = product_obj.uom_id and product_obj.uom_id.id
978             q = product_uom_obj._compute_qty(cr, uid, uom, qty, default_uom)
979             if product_obj.uos_id:
980                 result['product_uos'] = product_obj.uos_id.id
981                 result['product_uos_qty'] = qty * product_obj.uos_coeff
982             else:
983                 result['product_uos'] = False
984                 result['product_uos_qty'] = qty
985             result['th_weight'] = q * product_obj.weight        # Round the quantity up
986
987         # get unit price
988
989         if not pricelist:
990             warning = {
991                 'title': 'No Pricelist !',
992                 'message':
993                     'You have to select a pricelist in the sale form !\n'
994                     'Please set one before choosing a product.'
995                 }
996         else:
997             price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist],
998                     product, qty or 1.0, partner_id, {
999                         'uom': uom,
1000                         'date': date_order,
1001                         })[pricelist]
1002             if price is False:
1003                 warning = {
1004                     'title': 'No valid pricelist line found !',
1005                     'message':
1006                         "Couldn't find a pricelist line matching this product and quantity.\n"
1007                         "You have to change either the product, the quantity or the pricelist."
1008                     }
1009             else:
1010                 result.update({'price_unit': price})
1011         return {'value': result, 'domain': domain, 'warning': warning}
1012
1013     def product_uom_change(self, cursor, user, ids, pricelist, product, qty=0,
1014             uom=False, qty_uos=0, uos=False, name='', partner_id=False,
1015             lang=False, update_tax=True, date_order=False):
1016         res = self.product_id_change(cursor, user, ids, pricelist, product,
1017                 qty=0, uom=uom, qty_uos=qty_uos, uos=uos, name=name,
1018                 partner_id=partner_id, lang=lang, update_tax=update_tax,
1019                 date_order=date_order)
1020         if 'product_uom' in res['value']:
1021             del res['value']['product_uom']
1022         if not uom:
1023             res['value']['price_unit'] = 0.0
1024         return res
1025
1026     def unlink(self, cr, uid, ids, context={}):
1027         """Allows to delete sale order lines in draft,cancel states"""
1028         for rec in self.browse(cr, uid, ids, context=context):
1029             if rec.state not in ['draft', 'cancel']:
1030                 raise osv.except_osv(_('Invalid action !'), _('Cannot delete a sale order line which is %s !')%(rec.state,))
1031         return super(sale_order_line, self).unlink(cr, uid, ids, context=context)
1032
1033 sale_order_line()
1034
1035
1036 class sale_config_picking_policy(osv.osv_memory):
1037     _name = 'sale.config.picking_policy'
1038     _columns = {
1039         'name': fields.char('Name', size=64),
1040         'picking_policy': fields.selection([
1041             ('direct', 'Direct Delivery'),
1042             ('one', 'All at Once')
1043         ], 'Packing Default Policy', required=True),
1044         'order_policy': fields.selection([
1045             ('manual', 'Invoice Based on Sales Orders'),
1046             ('picking', 'Invoice Based on Deliveries'),
1047         ], 'Shipping Default Policy', required=True),
1048         'step': fields.selection([
1049             ('one', 'Delivery Order Only'),
1050             ('two', 'Packing List & Delivery Order')
1051         ], 'Steps To Deliver a Sale Order', required=True,
1052            help="By default, Open ERP is able to manage complex routing and paths "\
1053            "of products in your warehouse and partner locations. This will configure "\
1054            "the most common and simple methods to deliver products to the customer "\
1055            "in one or two operations by the worker.")
1056     }
1057     _defaults = {
1058         'picking_policy': lambda *a: 'direct',
1059         'order_policy': lambda *a: 'picking',
1060         'step': lambda *a: 'one'
1061     }
1062
1063     def set_default(self, cr, uid, ids, context=None):
1064         for o in self.browse(cr, uid, ids, context=context):
1065             ir_values_obj = self.pool.get('ir.values')
1066             ir_values_obj.set(cr, uid, 'default', False, 'picking_policy', ['sale.order'], o.picking_policy)
1067             ir_values_obj.set(cr, uid, 'default', False, 'order_policy', ['sale.order'], o.order_policy)
1068
1069             if o.step == 'one':
1070                 md = self.pool.get('ir.model.data')
1071                 group_id = md._get_id(cr, uid, 'base', 'group_no_one')
1072                 group_id = md.browse(cr, uid, group_id, context).res_id
1073                 menu_id = md._get_id(cr, uid, 'stock', 'menu_action_picking_tree_delivery')
1074                 menu_id = md.browse(cr, uid, menu_id, context).res_id
1075                 self.pool.get('ir.ui.menu').write(cr, uid, [menu_id], {'groups_id': [(6, 0, [group_id])]})
1076
1077                 location_id = md._get_id(cr, uid, 'stock', 'stock_location_output')
1078                 location_id = md.browse(cr, uid, location_id, context).res_id
1079                 self.pool.get('stock.location').write(cr, uid, [location_id], {'chained_auto_packing': 'transparent'})
1080
1081         return {
1082                 'view_type': 'form',
1083                 "view_mode": 'form',
1084                 'res_model': 'ir.actions.configuration.wizard',
1085                 'type': 'ir.actions.act_window',
1086                 'target': 'new',
1087          }
1088
1089     def action_cancel(self, cr, uid, ids, context=None):
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 sale_config_picking_policy()
1099