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