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