1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
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.
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.
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/>.
20 ##############################################################################
22 from datetime import datetime, timedelta
23 from dateutil.relativedelta import relativedelta
26 from osv import fields, osv
27 from tools.translate import _
28 import decimal_precision as dp
31 class sale_shop(osv.osv):
33 _description = "Sale Shop"
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 'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse'),
38 'pricelist_id': fields.many2one('product.pricelist', 'Pricelist'),
39 'project_id': fields.many2one('account.analytic.account', 'Analytic Account', domain=[('parent_id', '!=', False)]),
40 'company_id': fields.many2one('res.company', 'Company', required=False),
43 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'sale.shop', context=c),
48 class sale_order(osv.osv):
50 _description = "Sale Order"
52 def copy(self, cr, uid, id, default=None, context=None):
62 'date_confirm': False,
63 'name': self.pool.get('ir.sequence').get(cr, uid, 'sale.order'),
65 return super(sale_order, self).copy(cr, uid, id, default, context=context)
67 def _amount_line_tax(self, cr, uid, line, context=None):
71 for c in self.pool.get('account.tax').compute_all(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)['taxes']:
72 val += c.get('amount', 0.0)
75 def _amount_all(self, cr, uid, ids, field_name, arg, context=None):
76 cur_obj = self.pool.get('res.currency')
80 for order in self.browse(cr, uid, ids, context=context):
82 'amount_untaxed': 0.0,
87 cur = order.pricelist_id.currency_id
88 for line in order.order_line:
89 val1 += line.price_subtotal
90 val += self._amount_line_tax(cr, uid, line, context=context)
91 res[order.id]['amount_tax'] = cur_obj.round(cr, uid, cur, val)
92 res[order.id]['amount_untaxed'] = cur_obj.round(cr, uid, cur, val1)
93 res[order.id]['amount_total'] = res[order.id]['amount_untaxed'] + res[order.id]['amount_tax']
97 def _picked_rate(self, cr, uid, ids, name, arg, context=None):
106 p.sale_id, sum(m.product_qty), mp.state as mp_state
110 stock_picking p on (p.id=m.picking_id)
112 procurement_order mp on (mp.move_id=m.id)
114 p.sale_id IN %s GROUP BY mp.state, p.sale_id''', (tuple(ids),))
115 for oid, nbr, mp_state in cr.fetchall():
116 if mp_state == 'cancel':
118 if mp_state == 'done':
119 res[oid][0] += nbr or 0.0
120 res[oid][1] += nbr or 0.0
122 res[oid][1] += nbr or 0.0
127 res[r] = 100.0 * res[r][0] / res[r][1]
128 for order in self.browse(cr, uid, ids, context=context):
130 res[order.id] = 100.0
133 def _invoiced_rate(self, cursor, user, ids, name, arg, context=None):
137 for sale in self.browse(cursor, user, ids, context=context):
142 for invoice in sale.invoice_ids:
143 if invoice.state not in ('draft', 'cancel'):
144 tot += invoice.amount_untaxed
146 res[sale.id] = min(100.0, tot * 100.0 / (sale.amount_untaxed or 1.00))
151 def _invoiced(self, cursor, user, ids, name, arg, context=None):
155 for sale in self.browse(cursor, user, ids, context=context):
157 for invoice in sale.invoice_ids:
158 if invoice.state != 'paid':
161 if not sale.invoice_ids:
165 def _invoiced_search(self, cursor, user, obj, name, args, context=None):
176 clause += 'AND inv.state = \'paid\''
178 clause += 'AND inv.state != \'cancel\' AND sale.state != \'cancel\' AND inv.state <> \'paid\' AND rel.order_id = sale.id '
179 sale_clause = ', sale_order AS sale '
182 cursor.execute('SELECT rel.order_id ' \
183 'FROM sale_order_invoice_rel AS rel, account_invoice AS inv '+ sale_clause + \
184 'WHERE rel.invoice_id = inv.id ' + clause)
185 res = cursor.fetchall()
187 cursor.execute('SELECT sale.id ' \
188 'FROM sale_order AS sale ' \
189 'WHERE sale.id NOT IN ' \
190 '(SELECT rel.order_id ' \
191 'FROM sale_order_invoice_rel AS rel) and sale.state != \'cancel\'')
192 res.extend(cursor.fetchall())
194 return [('id', '=', 0)]
195 return [('id', 'in', [x[0] for x in res])]
197 def _get_order(self, cr, uid, ids, context=None):
201 for line in self.pool.get('sale.order.line').browse(cr, uid, ids, context=context):
202 result[line.order_id.id] = True
206 'name': fields.char('Order Reference', size=64, required=True,
207 readonly=True, states={'draft': [('readonly', False)]}, select=True),
208 'shop_id': fields.many2one('sale.shop', 'Shop', required=True, readonly=True, states={'draft': [('readonly', False)]}),
209 'origin': fields.char('Source document', size=64, help="Reference of the document that generated this sale order request."),
210 'client_order_ref': fields.char('Customer Reference', size=64),
211 'state': fields.selection([
212 ('draft', 'Quotation'),
213 ('waiting_date', 'Waiting Schedule'),
214 ('manual', 'Manual In Progress'),
215 ('progress', 'In Progress'),
216 ('shipping_except', 'Shipping Exception'),
217 ('invoice_except', 'Invoice Exception'),
219 ('cancel', 'Cancelled')
220 ], 'Order State', readonly=True, help="Gives the state of the quotation or sale order. \nThe exception state is automatically set when a cancel operation occurs in the invoice validation (Invoice Exception) or in the picking list process (Shipping Exception). \nThe 'Waiting Schedule' state is set when the invoice is confirmed but waiting for the scheduler to run on the date 'Ordered Date'.", select=True),
221 'date_order': fields.date('Ordered Date', required=True, readonly=True, states={'draft': [('readonly', False)]}),
222 'create_date': fields.date('Creation Date', readonly=True, help="Date on which sale order is created"),
223 'date_confirm': fields.date('Confirmation Date', readonly=True, help="Date on which sale order is confirmed"),
224 'user_id': fields.many2one('res.users', 'Salesman', states={'draft': [('readonly', False)]}, select=True),
225 'partner_id': fields.many2one('res.partner', 'Customer', readonly=True, states={'draft': [('readonly', False)]}, required=True, change_default=True, select=True),
226 'partner_invoice_id': fields.many2one('res.partner.address', 'Invoice Address', readonly=True, required=True, states={'draft': [('readonly', False)]}, help="Invoice address for current sale order"),
227 '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 who requested the order or quotation."),
228 'partner_shipping_id': fields.many2one('res.partner.address', 'Shipping Address', readonly=True, required=True, states={'draft': [('readonly', False)]}, help="Shipping address for current sale order"),
230 'incoterm': fields.many2one('stock.incoterms', 'Incoterm', help="Incoterm which stands for 'International Commercial terms' implies its a series of sales terms which are used in the commercial transaction"),
231 'picking_policy': fields.selection([('direct', 'Partial Delivery'), ('one', 'Complete Delivery')],
232 'Picking Policy', required=True, readonly=True, states={'draft': [('readonly', False)]}, help="""If you don't have enough stock available to deliver all at once, do you accept partial shipments or not?"""),
233 'order_policy': fields.selection([
234 ('prepaid', 'Payment Before Delivery'),
235 ('manual', 'Shipping & Manual Invoice'),
236 ('postpaid', 'Invoice on Order After Delivery'),
237 ('picking', 'Invoice from Delivery'),
238 ], 'Shipping Policy', required=True, readonly=True, states={'draft': [('readonly', False)]},
239 help="""The Shipping Policy is used to synchronise invoice and delivery operations.
240 - The 'Pay before delivery' choice will first generate the invoice and then generate the picking order after the payment of this invoice.
241 - The 'Shipping & Manual Invoice' will create the picking order directly and wait for the user to manually click on the 'Invoice' button to generate the draft invoice.
242 - The 'Invoice on Order After Delivery' choice will generate the draft invoice based on sale order after all picking lists have been finished.
243 - The 'Invoice from the picking' choice is used to create an invoice during the picking process."""),
244 'pricelist_id': fields.many2one('product.pricelist', 'Pricelist', required=True, readonly=True, states={'draft': [('readonly', False)]}, help="Pricelist version for current sale order"),
245 'project_id': fields.many2one('account.analytic.account', 'Analytic Account', readonly=True, states={'draft': [('readonly', False)]}, help="The analytic account related to a sale order"),
247 'order_line': fields.one2many('sale.order.line', 'order_id', 'Order Lines', readonly=True, states={'draft': [('readonly', False)]}),
248 'invoice_ids': fields.many2many('account.invoice', 'sale_order_invoice_rel', 'order_id', 'invoice_id', 'Invoices', readonly=True, 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)."),
249 '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"),
250 'shipped': fields.boolean('Delivered', readonly=True, help="It indicates that the sale order has been delivered. This field is updated only after the scheduler have been launched !"),
251 'picked_rate': fields.function(_picked_rate, method=True, string='Picked', type='float'),
252 'invoiced_rate': fields.function(_invoiced_rate, method=True, string='Invoiced', type='float'),
253 'invoiced': fields.function(_invoiced, method=True, string='Paid',
254 fnct_search=_invoiced_search, type='boolean', help="It indicates that an invoice has been paid"),
255 'note': fields.text('Notes'),
257 'amount_untaxed': fields.function(_amount_all, method=True, digits_compute= dp.get_precision('Sale Price'), string='Untaxed Amount',
259 'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line'], 10),
260 'sale.order.line': (_get_order, ['price_unit', 'tax_id', 'discount', 'product_uom_qty'], 10),
262 multi='sums', help="The amount without tax"),
263 'amount_tax': fields.function(_amount_all, method=True, digits_compute= dp.get_precision('Sale Price'), string='Taxes',
265 'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line'], 10),
266 'sale.order.line': (_get_order, ['price_unit', 'tax_id', 'discount', 'product_uom_qty'], 10),
268 multi='sums', help="The tax amount"),
269 'amount_total': fields.function(_amount_all, method=True, digits_compute= dp.get_precision('Sale Price'), string='Total',
271 'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line'], 10),
272 'sale.order.line': (_get_order, ['price_unit', 'tax_id', 'discount', 'product_uom_qty'], 10),
274 multi='sums', help="The total amount"),
276 '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 want your invoice based on ordered or shipped quantities. If the product is a service, shipped quantities means hours spent on the associated tasks.", required=True),
277 'payment_term': fields.many2one('account.payment.term', 'Payment Term'),
278 'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position'),
279 'company_id': fields.related('shop_id','company_id',type='many2one',relation='res.company',string='Company',store=True)
282 'picking_policy': 'direct',
283 'date_order': lambda *a: time.strftime('%Y-%m-%d'),
284 'order_policy': 'manual',
286 'user_id': lambda obj, cr, uid, context: uid,
287 'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'sale.order'),
288 'invoice_quantity': 'order',
289 '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'],
290 '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'],
291 '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'],
296 def unlink(self, cr, uid, ids, context=None):
299 sale_orders = self.read(cr, uid, ids, ['state'], context=context)
301 for s in sale_orders:
302 if s['state'] in ['draft', 'cancel']:
303 unlink_ids.append(s['id'])
305 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Sale Order(s) which are already confirmed !'))
306 return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
308 def onchange_shop_id(self, cr, uid, ids, shop_id):
311 shop = self.pool.get('sale.shop').browse(cr, uid, shop_id)
312 v['project_id'] = shop.project_id.id
313 # Que faire si le client a une pricelist a lui ?
314 if shop.pricelist_id.id:
315 v['pricelist_id'] = shop.pricelist_id.id
318 def action_cancel_draft(self, cr, uid, ids, *args):
321 cr.execute('select id from sale_order_line where order_id IN %s and state=%s', (tuple(ids), 'cancel'))
322 line_ids = map(lambda x: x[0], cr.fetchall())
323 self.write(cr, uid, ids, {'state': 'draft', 'invoice_ids': [], 'shipped': 0})
324 self.pool.get('sale.order.line').write(cr, uid, line_ids, {'invoiced': False, 'state': 'draft', 'invoice_lines': [(6, 0, [])]})
325 wf_service = netsvc.LocalService("workflow")
327 # Deleting the existing instance of workflow for SO
328 wf_service.trg_delete(uid, 'sale.order', inv_id, cr)
329 wf_service.trg_create(uid, 'sale.order', inv_id, cr)
330 for (id,name) in self.name_get(cr, uid, ids):
331 message = _("The sale order '%s' has been set in draft state.") %(name,)
332 self.log(cr, uid, id, message)
335 def onchange_partner_id(self, cr, uid, ids, part):
337 return {'value': {'partner_invoice_id': False, 'partner_shipping_id': False, 'partner_order_id': False, 'payment_term': False, 'fiscal_position': False}}
339 addr = self.pool.get('res.partner').address_get(cr, uid, [part], ['delivery', 'invoice', 'contact'])
340 part = self.pool.get('res.partner').browse(cr, uid, part)
341 pricelist = part.property_product_pricelist and part.property_product_pricelist.id or False
342 payment_term = part.property_payment_term and part.property_payment_term.id or False
343 fiscal_position = part.property_account_position and part.property_account_position.id or False
344 dedicated_salesman = part.user_id and part.user_id.id or uid
346 'partner_invoice_id': addr['invoice'],
347 'partner_order_id': addr['contact'],
348 'partner_shipping_id': addr['delivery'],
349 'payment_term': payment_term,
350 'fiscal_position': fiscal_position,
351 'user_id': dedicated_salesman,
354 val['pricelist_id'] = pricelist
355 return {'value': val}
357 def shipping_policy_change(self, cr, uid, ids, policy, context=None):
363 if policy == 'prepaid':
365 elif policy == 'picking':
366 inv_qty = 'procurement'
367 return {'value': {'invoice_quantity': inv_qty}}
369 def write(self, cr, uid, ids, vals, context=None):
372 if vals.get('order_policy', False):
373 if vals['order_policy'] == 'prepaid':
374 vals.update({'invoice_quantity': 'order'})
375 elif vals['order_policy'] == 'picking':
376 vals.update({'invoice_quantity': 'procurement'})
377 return super(sale_order, self).write(cr, uid, ids, vals, context=context)
379 def create(self, cr, uid, vals, context=None):
382 if vals.get('order_policy', False):
383 if vals['order_policy'] == 'prepaid':
384 vals.update({'invoice_quantity': 'order'})
385 if vals['order_policy'] == 'picking':
386 vals.update({'invoice_quantity': 'procurement'})
387 return super(sale_order, self).create(cr, uid, vals, context=context)
389 def button_dummy(self, cr, uid, ids, context=None):
394 #FIXME: the method should return the list of invoices created (invoice_ids)
395 # and not the id of the last invoice created (res). The problem is that we
396 # cannot change it directly since the method is called by the sale order
397 # workflow and I suppose it expects a single id...
398 def _inv_get(self, cr, uid, order, context=None):
403 def _make_invoice(self, cr, uid, order, lines, context=None):
404 journal_obj = self.pool.get('account.journal')
405 inv_obj = self.pool.get('account.invoice')
406 obj_invoice_line = self.pool.get('account.invoice.line')
410 journal_ids = journal_obj.search(cr, uid, [('type', '=', 'sale'), ('company_id', '=', order.company_id.id)], limit=1)
412 raise osv.except_osv(_('Error !'),
413 _('There is no sale journal defined for this company: "%s" (id:%d)') % (order.company_id.name, order.company_id.id))
414 a = order.partner_id.property_account_receivable.id
415 pay_term = order.payment_term and order.payment_term.id or False
416 invoiced_sale_line_ids = self.pool.get('sale.order.line').search(cr, uid, [('order_id', '=', order.id), ('invoiced', '=', True)], context=context)
417 from_line_invoice_ids = []
418 for invoiced_sale_line_id in self.pool.get('sale.order.line').browse(cr, uid, invoiced_sale_line_ids, context=context):
419 for invoice_line_id in invoiced_sale_line_id.invoice_lines:
420 if invoice_line_id.invoice_id.id not in from_line_invoice_ids:
421 from_line_invoice_ids.append(invoice_line_id.invoice_id.id)
422 for preinv in order.invoice_ids:
423 if preinv.state not in ('cancel',) and preinv.id not in from_line_invoice_ids:
424 for preline in preinv.invoice_line:
425 inv_line_id = obj_invoice_line.copy(cr, uid, preline.id, {'invoice_id': False, 'price_unit': -preline.price_unit})
426 lines.append(inv_line_id)
428 'name': order.client_order_ref or order.name,
429 'origin': order.name,
430 'type': 'out_invoice',
431 'reference': "P%dSO%d" % (order.partner_id.id, order.id),
433 'partner_id': order.partner_id.id,
434 'journal_id': journal_ids[0],
435 'address_invoice_id': order.partner_invoice_id.id,
436 'address_contact_id': order.partner_order_id.id,
437 'invoice_line': [(6, 0, lines)],
438 'currency_id': order.pricelist_id.currency_id.id,
439 'comment': order.note,
440 'payment_term': pay_term,
441 'fiscal_position': order.fiscal_position.id or order.partner_id.property_account_position.id,
442 'date_invoice': context.get('date_invoice',False),
443 'company_id': order.company_id.id,
444 'user_id': order.user_id and order.user_id.id or False
446 inv.update(self._inv_get(cr, uid, order))
447 inv_id = inv_obj.create(cr, uid, inv, context=context)
448 data = inv_obj.onchange_payment_term_date_invoice(cr, uid, [inv_id], pay_term, time.strftime('%Y-%m-%d'))
449 if data.get('value', False):
450 inv_obj.write(cr, uid, [inv_id], data['value'], context=context)
451 inv_obj.button_compute(cr, uid, [inv_id])
454 def manual_invoice(self, cr, uid, ids, context=None):
455 mod_obj = self.pool.get('ir.model.data')
456 wf_service = netsvc.LocalService("workflow")
460 for record in self.pool.get('sale.order').browse(cr, uid, id).invoice_ids:
461 inv_ids.add(record.id)
462 # inv_ids would have old invoices if any
464 wf_service.trg_validate(uid, 'sale.order', id, 'manual_invoice', cr)
465 for record in self.pool.get('sale.order').browse(cr, uid, id).invoice_ids:
466 inv_ids1.add(record.id)
467 inv_ids = list(inv_ids1.difference(inv_ids))
469 res = mod_obj.get_object_reference(cr, uid, 'account', 'invoice_form')
470 res_id = res and res[1] or False,
473 'name': 'Customer Invoices',
477 'res_model': 'account.invoice',
478 'context': "{'type':'out_invoice'}",
479 'type': 'ir.actions.act_window',
482 'res_id': inv_ids and inv_ids[0] or False,
485 def action_invoice_create(self, cr, uid, ids, grouped=False, states=['confirmed', 'done', 'exception'], date_inv = False, context=None):
489 picking_obj = self.pool.get('stock.picking')
490 invoice = self.pool.get('account.invoice')
491 obj_sale_order_line = self.pool.get('sale.order.line')
494 # If date was specified, use it as date invoiced, usefull when invoices are generated this month and put the
495 # last day of the last month as invoice date
497 context['date_inv'] = date_inv
498 for o in self.browse(cr, uid, ids):
500 for line in o.order_line:
503 elif (line.state in states):
504 lines.append(line.id)
505 created_lines = obj_sale_order_line.invoice_line_create(cr, uid, lines)
507 invoices.setdefault(o.partner_id.id, []).append((o, created_lines))
509 for o in self.browse(cr, uid, ids):
510 for i in o.invoice_ids:
511 if i.state == 'draft':
513 for val in invoices.values():
515 res = self._make_invoice(cr, uid, val[0][0], reduce(lambda x, y: x + y, [l for o, l in val], []), context=context)
518 invoice_ref += o.name + '|'
519 self.write(cr, uid, [o.id], {'state': 'progress'})
520 if o.order_policy == 'picking':
521 picking_obj.write(cr, uid, map(lambda x: x.id, o.picking_ids), {'invoice_state': 'invoiced'})
522 cr.execute('insert into sale_order_invoice_rel (order_id,invoice_id) values (%s,%s)', (o.id, res))
523 invoice.write(cr, uid, [res], {'origin': invoice_ref, 'name': invoice_ref})
525 for order, il in val:
526 res = self._make_invoice(cr, uid, order, il, context=context)
527 invoice_ids.append(res)
528 self.write(cr, uid, [order.id], {'state': 'progress'})
529 if order.order_policy == 'picking':
530 picking_obj.write(cr, uid, map(lambda x: x.id, order.picking_ids), {'invoice_state': 'invoiced'})
531 cr.execute('insert into sale_order_invoice_rel (order_id,invoice_id) values (%s,%s)', (order.id, res))
534 def action_invoice_cancel(self, cr, uid, ids, context=None):
537 for sale in self.browse(cr, uid, ids):
538 for line in sale.order_line:
540 # Check if the line is invoiced (has asociated invoice
541 # lines from non-cancelled invoices).
544 for iline in line.invoice_lines:
545 if iline.invoice_id and iline.invoice_id.state != 'cancel':
548 # Update the line (only when needed)
549 if line.invoiced != invoiced:
550 self.pool.get('sale.order.line').write(cr, uid, [line.id], {'invoiced': invoiced}, context=context)
551 self.write(cr, uid, ids, {'state': 'invoice_except', 'invoice_ids': False}, context=context)
554 def action_invoice_end(self, cr, uid, ids, context=None):
555 for order in self.browse(cr, uid, ids, context=context):
557 # Update the sale order lines state (and invoiced flag).
559 for line in order.order_line:
562 # Check if the line is invoiced (has asociated invoice
563 # lines from non-cancelled invoices).
566 for iline in line.invoice_lines:
567 if iline.invoice_id and iline.invoice_id.state != 'cancel':
570 if line.invoiced != invoiced:
571 vals['invoiced'] = invoiced
572 # If the line was in exception state, now it gets confirmed.
573 if line.state == 'exception':
574 vals['state'] = 'confirmed'
575 # Update the line (only when needed).
577 self.pool.get('sale.order.line').write(cr, uid, [line.id], vals, context=context)
579 # Update the sale order state.
581 if order.state == 'invoice_except':
582 self.write(cr, uid, [order.id], {'state': 'progress'}, context=context)
585 def action_cancel(self, cr, uid, ids, context=None):
586 wf_service = netsvc.LocalService("workflow")
589 sale_order_line_obj = self.pool.get('sale.order.line')
590 for sale in self.browse(cr, uid, ids, context=context):
591 for pick in sale.picking_ids:
592 if pick.state not in ('draft', 'cancel'):
593 raise osv.except_osv(
594 _('Could not cancel sale order !'),
595 _('You must first cancel all picking attached to this sale order.'))
596 for r in self.read(cr, uid, ids, ['picking_ids']):
597 for pick in r['picking_ids']:
598 wf_service.trg_validate(uid, 'stock.picking', pick, 'button_cancel', cr)
599 for inv in sale.invoice_ids:
600 if inv.state not in ('draft', 'cancel'):
601 raise osv.except_osv(
602 _('Could not cancel this sale order !'),
603 _('You must first cancel all invoices attached to this sale order.'))
604 for r in self.read(cr, uid, ids, ['invoice_ids']):
605 for inv in r['invoice_ids']:
606 wf_service.trg_validate(uid, 'account.invoice', inv, 'invoice_cancel', cr)
607 sale_order_line_obj.write(cr, uid, [l.id for l in sale.order_line],
609 message = _("The sale order '%s' has been cancelled.") % (sale.name,)
610 self.log(cr, uid, sale.id, message)
611 self.write(cr, uid, ids, {'state': 'cancel'})
614 def action_wait(self, cr, uid, ids, *args):
615 for o in self.browse(cr, uid, ids):
616 if (o.order_policy == 'manual'):
617 self.write(cr, uid, [o.id], {'state': 'manual', 'date_confirm': time.strftime('%Y-%m-%d')})
619 self.write(cr, uid, [o.id], {'state': 'progress', 'date_confirm': time.strftime('%Y-%m-%d')})
620 self.pool.get('sale.order.line').button_confirm(cr, uid, [x.id for x in o.order_line])
621 message = _("The quotation '%s' has been converted to a sale order.") % (o.name,)
622 self.log(cr, uid, o.id, message)
625 def procurement_lines_get(self, cr, uid, ids, *args):
627 for order in self.browse(cr, uid, ids, context={}):
628 for line in order.order_line:
629 if line.procurement_id:
630 res.append(line.procurement_id.id)
633 # if mode == 'finished':
634 # returns True if all lines are done, False otherwise
635 # if mode == 'canceled':
636 # returns True if there is at least one canceled line, False otherwise
637 def test_state(self, cr, uid, ids, mode, *args):
638 assert mode in ('finished', 'canceled'), _("invalid mode for test_state")
643 write_cancel_ids = []
644 for order in self.browse(cr, uid, ids, context={}):
645 for line in order.order_line:
646 if (not line.procurement_id) or (line.procurement_id.state=='done'):
647 if line.state != 'done':
648 write_done_ids.append(line.id)
651 if line.procurement_id:
652 if (line.procurement_id.state == 'cancel'):
654 if line.state != 'exception':
655 write_cancel_ids.append(line.id)
659 self.pool.get('sale.order.line').write(cr, uid, write_done_ids, {'state': 'done'})
661 self.pool.get('sale.order.line').write(cr, uid, write_cancel_ids, {'state': 'exception'})
663 if mode == 'finished':
665 elif mode == 'canceled':
671 def action_ship_create(self, cr, uid, ids, *args):
672 wf_service = netsvc.LocalService("workflow")
674 move_obj = self.pool.get('stock.move')
675 proc_obj = self.pool.get('procurement.order')
676 company = self.pool.get('res.users').browse(cr, uid, uid).company_id
677 for order in self.browse(cr, uid, ids, context={}):
679 output_id = order.shop_id.warehouse_id.lot_output_id.id
681 for line in order.order_line:
683 date_planned = datetime.now() + relativedelta(days=line.delay or 0.0)
684 date_planned = (date_planned - timedelta(days=company.security_lead)).strftime('%Y-%m-%d %H:%M:%S')
686 if line.state == 'done':
689 if line.product_id and line.product_id.product_tmpl_id.type in ('product', 'consu'):
690 location_id = order.shop_id.warehouse_id.lot_stock_id.id
692 pick_name = self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.out')
693 picking_id = self.pool.get('stock.picking').create(cr, uid, {
695 'origin': order.name,
698 'move_type': order.picking_policy,
700 'address_id': order.partner_shipping_id.id,
702 'invoice_state': (order.order_policy=='picking' and '2binvoiced') or 'none',
703 'company_id': order.company_id.id,
705 move_id = self.pool.get('stock.move').create(cr, uid, {
706 'name': line.name[:64],
707 'picking_id': picking_id,
708 'product_id': line.product_id.id,
709 'date': date_planned,
710 'date_expected': date_planned,
711 'product_qty': line.product_uom_qty,
712 'product_uom': line.product_uom.id,
713 'product_uos_qty': line.product_uos_qty,
714 'product_uos': (line.product_uos and line.product_uos.id)\
715 or line.product_uom.id,
716 'product_packaging': line.product_packaging.id,
717 'address_id': line.address_allotment_id.id or order.partner_shipping_id.id,
718 'location_id': location_id,
719 'location_dest_id': output_id,
720 'sale_line_id': line.id,
721 'tracking_id': False,
725 'company_id': order.company_id.id,
728 proc_id = self.pool.get('procurement.order').create(cr, uid, {
730 'origin': order.name,
731 'date_planned': date_planned,
732 'product_id': line.product_id.id,
733 'product_qty': line.product_uom_qty,
734 'product_uom': line.product_uom.id,
735 'product_uos_qty': (line.product_uos and line.product_uos_qty)\
736 or line.product_uom_qty,
737 'product_uos': (line.product_uos and line.product_uos.id)\
738 or line.product_uom.id,
739 'location_id': order.shop_id.warehouse_id.lot_stock_id.id,
740 'procure_method': line.type,
742 'property_ids': [(6, 0, [x.id for x in line.property_ids])],
743 'company_id': order.company_id.id,
745 proc_ids.append(proc_id)
746 self.pool.get('sale.order.line').write(cr, uid, [line.id], {'procurement_id': proc_id})
748 if line.state == 'exception':
749 for pick in order.picking_ids:
750 for moves in pick.move_lines:
751 if moves.state == 'cancel':
752 move_obj.write(cr, uid, [move_id], {'product_qty': moves.product_qty})
753 proc_obj.write(cr, uid, [proc_id], {'product_qty': moves.product_qty, 'product_uos_qty': moves.product_qty})
755 for proc_id in proc_ids:
756 wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_confirm', cr)
759 wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
761 if order.state == 'shipping_except':
762 val['state'] = 'progress'
763 val['shipped'] = False
765 if (order.order_policy == 'manual'):
766 for line in order.order_line:
767 if (not line.invoiced) and (line.state not in ('cancel', 'draft')):
768 val['state'] = 'manual'
770 self.write(cr, uid, [order.id], val)
773 def action_ship_end(self, cr, uid, ids, context=None):
776 for order in self.browse(cr, uid, ids, context=context):
777 val = {'shipped': True}
778 if order.state == 'shipping_except':
779 val['state'] = 'progress'
780 if (order.order_policy == 'manual'):
781 for line in order.order_line:
782 if (not line.invoiced) and (line.state not in ('cancel', 'draft')):
783 val['state'] = 'manual'
785 for line in order.order_line:
787 if line.state == 'exception':
788 towrite.append(line.id)
790 self.pool.get('sale.order.line').write(cr, uid, towrite, {'state': 'done'}, context=context)
791 self.write(cr, uid, [order.id], val)
794 def _log_event(self, cr, uid, ids, factor=0.7, name='Open Order'):
795 invs = self.read(cr, uid, ids, ['date_order', 'partner_id', 'amount_untaxed'])
797 part = inv['partner_id'] and inv['partner_id'][0]
798 pr = inv['amount_untaxed'] or 0.0
799 partnertype = 'customer'
802 'name': 'Order: '+name,
804 'description': 'Order '+str(inv['id']),
807 'date': time.strftime('%Y-%m-%d'),
810 'partner_type': partnertype,
812 'planned_revenue': pr,
816 self.pool.get('res.partner.event').create(cr, uid, event)
818 def has_stockable_products(self, cr, uid, ids, *args):
819 for order in self.browse(cr, uid, ids):
820 for order_line in order.order_line:
821 if order_line.product_id and order_line.product_id.product_tmpl_id.type in ('product', 'consu'):
826 # TODO add a field price_unit_uos
827 # - update it on change product and unit price
828 # - use it in report if there is a uos
829 class sale_order_line(osv.osv):
831 def _amount_line(self, cr, uid, ids, field_name, arg, context=None):
832 tax_obj = self.pool.get('account.tax')
833 cur_obj = self.pool.get('res.currency')
835 context = context or {}
836 for line in self.browse(cr, uid, ids, context=context):
837 price = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
838 taxes = tax_obj.compute_all(cr, uid, line.tax_id, price, line.product_uom_qty, line.order_id.partner_invoice_id.id, line.product_id, line.order_id.partner_id)
839 cur = line.order_id.pricelist_id.currency_id
840 res[line.id] = cur_obj.round(cr, uid, cur, taxes['total'])
843 def _number_packages(self, cr, uid, ids, field_name, arg, context=None):
847 for line in self.browse(cr, uid, ids, context=context):
849 res[line.id] = int((line.product_uom_qty+line.product_packaging.qty-0.0001) / line.product_packaging.qty)
854 _name = 'sale.order.line'
855 _description = 'Sale Order Line'
857 'order_id': fields.many2one('sale.order', 'Order Reference', required=True, ondelete='cascade', select=True, readonly=True, states={'draft':[('readonly',False)]}),
858 'name': fields.char('Description', size=256, required=True, select=True, readonly=True, states={'draft': [('readonly', False)]}),
859 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of sale order lines."),
860 'delay': fields.float('Delivery Lead Time', required=True, help="Number of days between the order confirmation the shipping of the products to the customer", readonly=True, states={'draft': [('readonly', False)]}),
861 'product_id': fields.many2one('product.product', 'Product', domain=[('sale_ok', '=', True)], change_default=True),
862 'invoice_lines': fields.many2many('account.invoice.line', 'sale_order_line_invoice_rel', 'order_line_id', 'invoice_id', 'Invoice Lines', readonly=True),
863 'invoiced': fields.boolean('Invoiced', readonly=True),
864 'procurement_id': fields.many2one('procurement.order', 'Procurement'),
865 'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Sale Price'), readonly=True, states={'draft': [('readonly', False)]}),
866 'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal', digits_compute= dp.get_precision('Sale Price')),
867 'tax_id': fields.many2many('account.tax', 'sale_order_tax', 'order_line_id', 'tax_id', 'Taxes', readonly=True, states={'draft': [('readonly', False)]}),
868 'type': fields.selection([('make_to_stock', 'from stock'), ('make_to_order', 'on order')], 'Procurement Method', required=True, readonly=True, states={'draft': [('readonly', False)]}),
869 'property_ids': fields.many2many('mrp.property', 'sale_order_line_property_rel', 'order_id', 'property_id', 'Properties', readonly=True, states={'draft': [('readonly', False)]}),
870 'address_allotment_id': fields.many2one('res.partner.address', 'Allotment Partner'),
871 'product_uom_qty': fields.float('Quantity (UoM)', digits=(16, 2), required=True, readonly=True, states={'draft': [('readonly', False)]}),
872 'product_uom': fields.many2one('product.uom', 'Unit of Measure ', required=True, readonly=True, states={'draft': [('readonly', False)]}),
873 'product_uos_qty': fields.float('Quantity (UoS)', readonly=True, states={'draft': [('readonly', False)]}),
874 'product_uos': fields.many2one('product.uom', 'Product UoS'),
875 'product_packaging': fields.many2one('product.packaging', 'Packaging'),
876 'move_ids': fields.one2many('stock.move', 'sale_line_id', 'Inventory Moves', readonly=True),
877 'discount': fields.float('Discount (%)', digits=(16, 2), readonly=True, states={'draft': [('readonly', False)]}),
878 'number_packages': fields.function(_number_packages, method=True, type='integer', string='Number Packages'),
879 'notes': fields.text('Notes'),
880 'th_weight': fields.float('Weight', readonly=True, states={'draft': [('readonly', False)]}),
881 'state': fields.selection([('draft', 'Draft'),('confirmed', 'Confirmed'),('done', 'Done'),('cancel', 'Cancelled'),('exception', 'Exception')], 'State', required=True, readonly=True,
882 help='* The \'Draft\' state is set when the related sale order in draft state. \
883 \n* The \'Confirmed\' state is set when the related sale order is confirmed. \
884 \n* The \'Exception\' state is set when the related sale order is set as exception. \
885 \n* The \'Done\' state is set when the sale order line has been picked. \
886 \n* The \'Cancelled\' state is set when a user cancel the sale order related.'),
887 'order_partner_id': fields.related('order_id', 'partner_id', type='many2one', relation='res.partner', string='Customer'),
888 'salesman_id':fields.related('order_id', 'user_id', type='many2one', relation='res.users', store=True, string='Salesman'),
889 'company_id': fields.related('order_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True, states={'draft': [('readonly', False)]}),
891 _order = 'sequence, id desc'
895 'product_uom_qty': 1,
896 'product_uos_qty': 1,
900 'type': 'make_to_stock',
901 'product_packaging': False
904 def invoice_line_create(self, cr, uid, ids, context=None):
908 def _get_line_qty(line):
909 if (line.order_id.invoice_quantity=='order') or not line.procurement_id:
911 return line.product_uos_qty or 0.0
912 return line.product_uom_qty
914 return self.pool.get('procurement.order').quantity_get(cr, uid,
915 line.procurement_id.id, context=context)
917 def _get_line_uom(line):
918 if (line.order_id.invoice_quantity=='order') or not line.procurement_id:
920 return line.product_uos.id
921 return line.product_uom.id
923 return self.pool.get('procurement.order').uom_get(cr, uid,
924 line.procurement_id.id, context=context)
928 for line in self.browse(cr, uid, ids, context=context):
929 if not line.invoiced:
931 a = line.product_id.product_tmpl_id.property_account_income.id
933 a = line.product_id.categ_id.property_account_income_categ.id
935 raise osv.except_osv(_('Error !'),
936 _('There is no income account defined ' \
937 'for this product: "%s" (id:%d)') % \
938 (line.product_id.name, line.product_id.id,))
940 prop = self.pool.get('ir.property').get(cr, uid,
941 'property_account_income_categ', 'product.category',
943 a = prop and prop.id or False
944 uosqty = _get_line_qty(line)
945 uos_id = _get_line_uom(line)
948 pu = round(line.price_unit * line.product_uom_qty / uosqty,
949 self.pool.get('decimal.precision').precision_get(cr, uid, 'Sale Price'))
950 fpos = line.order_id.fiscal_position or False
951 a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
953 raise osv.except_osv(_('Error !'),
954 _('There is no income category account defined in default Properties for Product Category or Fiscal Position is not defined !'))
955 inv_id = self.pool.get('account.invoice.line').create(cr, uid, {
957 'origin': line.order_id.name,
961 'discount': line.discount,
963 'product_id': line.product_id.id or False,
964 'invoice_line_tax_id': [(6, 0, [x.id for x in line.tax_id])],
966 'account_analytic_id': line.order_id.project_id and line.order_id.project_id.id or False,
968 cr.execute('insert into sale_order_line_invoice_rel (order_line_id,invoice_id) values (%s,%s)', (line.id, inv_id))
969 self.write(cr, uid, [line.id], {'invoiced': True})
970 sales[line.order_id.id] = True
971 create_ids.append(inv_id)
972 # Trigger workflow events
973 wf_service = netsvc.LocalService("workflow")
974 for sid in sales.keys():
975 wf_service.trg_write(uid, 'sale.order', sid, cr)
978 def button_cancel(self, cr, uid, ids, context=None):
981 for line in self.browse(cr, uid, ids, context=context):
983 raise osv.except_osv(_('Invalid action !'), _('You cannot cancel a sale order line that has already been invoiced !'))
984 for move_line in line.move_ids:
985 if move_line.state != 'cancel':
986 raise osv.except_osv(
987 _('Could not cancel sale order line!'),
988 _('You must first cancel stock moves attached to this sale order line.'))
989 return self.write(cr, uid, ids, {'state': 'cancel'})
991 def button_confirm(self, cr, uid, ids, context=None):
994 return self.write(cr, uid, ids, {'state': 'confirmed'})
996 def button_done(self, cr, uid, ids, context=None):
999 wf_service = netsvc.LocalService("workflow")
1000 res = self.write(cr, uid, ids, {'state': 'done'})
1001 for line in self.browse(cr, uid, ids, context=context):
1002 wf_service.trg_write(uid, 'sale.order', line.order_id.id, cr)
1005 def uos_change(self, cr, uid, ids, product_uos, product_uos_qty=0, product_id=None):
1006 product_obj = self.pool.get('product.product')
1008 return {'value': {'product_uom': product_uos,
1009 'product_uom_qty': product_uos_qty}, 'domain': {}}
1011 product = product_obj.browse(cr, uid, product_id)
1013 'product_uom': product.uom_id.id,
1015 # FIXME must depend on uos/uom of the product and not only of the coeff.
1018 'product_uom_qty': product_uos_qty / product.uos_coeff,
1019 'th_weight': product_uos_qty / product.uos_coeff * product.weight
1021 except ZeroDivisionError:
1023 return {'value': value}
1025 def copy_data(self, cr, uid, id, default=None, context=None):
1030 default.update({'state': 'draft', 'move_ids': [], 'invoiced': False, 'invoice_lines': []})
1031 return super(sale_order_line, self).copy_data(cr, uid, id, default, context=context)
1033 def product_id_change(self, cr, uid, ids, pricelist, product, qty=0,
1034 uom=False, qty_uos=0, uos=False, name='', partner_id=False,
1035 lang=False, update_tax=True, date_order=False, packaging=False, fiscal_position=False, flag=False):
1037 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.'))
1039 product_uom_obj = self.pool.get('product.uom')
1040 partner_obj = self.pool.get('res.partner')
1041 product_obj = self.pool.get('product.product')
1043 lang = partner_obj.browse(cr, uid, partner_id).lang
1044 context = {'lang': lang, 'partner_id': partner_id}
1047 return {'value': {'th_weight': 0, 'product_packaging': False,
1048 'product_uos_qty': qty}, 'domain': {'product_uom': [],
1051 date_order = time.strftime('%Y-%m-%d')
1054 product_obj = product_obj.browse(cr, uid, product, context=context)
1055 if not packaging and product_obj.packaging:
1056 packaging = product_obj.packaging[0].id
1057 result['product_packaging'] = packaging
1060 default_uom = product_obj.uom_id and product_obj.uom_id.id
1061 pack = self.pool.get('product.packaging').browse(cr, uid, packaging, context=context)
1062 q = product_uom_obj._compute_qty(cr, uid, uom, pack.qty, default_uom)
1063 # qty = qty - qty % q + q
1064 if qty and (q and not (qty % q) == 0):
1068 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)
1069 warn_msg = warn_msg + "\n\n" + _("EAN: ") + str(ean) + _(" Quantity: ") + str(qty_pack) + _(" Type of ul: ") + str(type_ul.name)
1071 'title': _('Picking Information !'),
1074 result['product_uom_qty'] = qty
1078 uom2 = product_uom_obj.browse(cr, uid, uom)
1079 if product_obj.uom_id.category_id.id != uom2.category_id.id:
1082 if product_obj.uos_id:
1083 uos2 = product_uom_obj.browse(cr, uid, uos)
1084 if product_obj.uos_id.category_id.id != uos2.category_id.id:
1088 if product_obj.description_sale:
1089 result['notes'] = product_obj.description_sale
1090 fpos = fiscal_position and self.pool.get('account.fiscal.position').browse(cr, uid, fiscal_position) or False
1091 if update_tax: #The quantity only have changed
1092 result['delay'] = (product_obj.sale_delay or 0.0)
1093 result['tax_id'] = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, product_obj.taxes_id)
1094 result.update({'type': product_obj.procure_method})
1097 result['name'] = self.pool.get('product.product').name_get(cr, uid, [product_obj.id], context=context)[0][1]
1099 if (not uom) and (not uos):
1100 result['product_uom'] = product_obj.uom_id.id
1101 if product_obj.uos_id:
1102 result['product_uos'] = product_obj.uos_id.id
1103 result['product_uos_qty'] = qty * product_obj.uos_coeff
1104 uos_category_id = product_obj.uos_id.category_id.id
1106 result['product_uos'] = False
1107 result['product_uos_qty'] = qty
1108 uos_category_id = False
1109 result['th_weight'] = qty * product_obj.weight
1110 domain = {'product_uom':
1111 [('category_id', '=', product_obj.uom_id.category_id.id)],
1113 [('category_id', '=', uos_category_id)]}
1115 elif uos and not uom: # only happens if uom is False
1116 result['product_uom'] = product_obj.uom_id and product_obj.uom_id.id
1117 result['product_uom_qty'] = qty_uos / product_obj.uos_coeff
1118 result['th_weight'] = result['product_uom_qty'] * product_obj.weight
1119 elif uom: # whether uos is set or not
1120 default_uom = product_obj.uom_id and product_obj.uom_id.id
1121 q = product_uom_obj._compute_qty(cr, uid, uom, qty, default_uom)
1122 if product_obj.uos_id:
1123 result['product_uos'] = product_obj.uos_id.id
1124 result['product_uos_qty'] = qty * product_obj.uos_coeff
1126 result['product_uos'] = False
1127 result['product_uos_qty'] = qty
1128 result['th_weight'] = q * product_obj.weight # Round the quantity up
1131 uom2 = product_obj.uom_id
1132 if (product_obj.type=='product') and (product_obj.virtual_available * uom2.factor < qty * product_obj.uom_id.factor) \
1133 and (product_obj.procure_method=='make_to_stock'):
1135 'title': _('Not enough stock !'),
1136 'message': _('You plan to sell %.2f %s but you only have %.2f %s available !\nThe real stock is %.2f %s. (without reservations)') %
1137 (qty, uom2 and uom2.name or product_obj.uom_id.name,
1138 max(0,product_obj.virtual_available), product_obj.uom_id.name,
1139 max(0,product_obj.qty_available), product_obj.uom_id.name)
1144 'title': 'No Pricelist !',
1146 'You have to select a pricelist or a customer in the sale form !\n'
1147 'Please set one before choosing a product.'
1150 price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist],
1151 product, qty or 1.0, partner_id, {
1157 'title': 'No valid pricelist line found !',
1159 "Couldn't find a pricelist line matching this product and quantity.\n"
1160 "You have to change either the product, the quantity or the pricelist."
1163 result.update({'price_unit': price})
1164 return {'value': result, 'domain': domain, 'warning': warning}
1166 def product_uom_change(self, cursor, user, ids, pricelist, product, qty=0,
1167 uom=False, qty_uos=0, uos=False, name='', partner_id=False,
1168 lang=False, update_tax=True, date_order=False):
1169 res = self.product_id_change(cursor, user, ids, pricelist, product,
1170 qty=qty, uom=uom, qty_uos=qty_uos, uos=uos, name=name,
1171 partner_id=partner_id, lang=lang, update_tax=update_tax,
1172 date_order=date_order)
1173 if 'product_uom' in res['value']:
1174 del res['value']['product_uom']
1176 res['value']['price_unit'] = 0.0
1179 def unlink(self, cr, uid, ids, context=None):
1182 """Allows to delete sale order lines in draft,cancel states"""
1183 for rec in self.browse(cr, uid, ids, context=context):
1184 if rec.state not in ['draft', 'cancel']:
1185 raise osv.except_osv(_('Invalid action !'), _('Cannot delete a sale order line which is %s !') %(rec.state,))
1186 return super(sale_order_line, self).unlink(cr, uid, ids, context=context)
1190 class sale_config_picking_policy(osv.osv_memory):
1191 _name = 'sale.config.picking_policy'
1192 _inherit = 'res.config'
1195 'name': fields.char('Name', size=64),
1196 'picking_policy': fields.selection([
1197 ('direct', 'Direct Delivery'),
1198 ('one', 'All at Once')
1199 ], 'Picking Default Policy', required=True, help="The Shipping Policy is used to configure per order if you want to deliver as soon as possible when one product is available or you wait that all products are available.."),
1200 'order_policy': fields.selection([
1201 ('manual', 'Invoice Based on Sales Orders'),
1202 ('picking', 'Invoice Based on Deliveries'),
1203 ], 'Shipping Default Policy', required=True,
1204 help="You can generate invoices based on sales orders or based on shippings."),
1205 'step': fields.selection([
1206 ('one', 'Delivery Order Only'),
1207 ('two', 'Picking List & Delivery Order')
1208 ], 'Steps To Deliver a Sale Order', required=True,
1209 help="By default, OpenERP is able to manage complex routing and paths "\
1210 "of products in your warehouse and partner locations. This will configure "\
1211 "the most common and simple methods to deliver products to the customer "\
1212 "in one or two operations by the worker.")
1215 'picking_policy': 'direct',
1216 'order_policy': 'manual',
1220 def execute(self, cr, uid, ids, context=None):
1223 for o in self.browse(cr, uid, ids, context=context):
1224 ir_values_obj = self.pool.get('ir.values')
1225 ir_values_obj.set(cr, uid, 'default', False, 'picking_policy', ['sale.order'], o.picking_policy)
1226 ir_values_obj.set(cr, uid, 'default', False, 'order_policy', ['sale.order'], o.order_policy)
1228 md = self.pool.get('ir.model.data')
1229 location_id = md.get_object_reference(cr, uid, 'stock', 'stock_location_output')
1230 location_id = location_id and location_id[1] or False
1231 self.pool.get('stock.location').write(cr, uid, [location_id], {'chained_auto_packing': 'transparent'})
1233 sale_config_picking_policy()
1235 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: