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 ##############################################################################
23 from datetime import datetime
24 from dateutil.relativedelta import relativedelta
27 from osv import fields, osv
28 from tools import config
29 from tools.translate import _
31 import decimal_precision as dp
34 class sale_shop(osv.osv):
36 _description = "Sale Shop"
38 'name': fields.char('Shop Name', size=64, required=True),
39 'payment_default_id': fields.many2one('account.payment.term', 'Default Payment Term', required=True),
40 'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse'),
41 'pricelist_id': fields.many2one('product.pricelist', 'Pricelist'),
42 'project_id': fields.many2one('account.analytic.account', 'Analytic Account'),
43 'company_id': fields.many2one('res.company', 'Company'),
48 def _incoterm_get(self, cr, uid, context=None):
51 cr.execute('select code, code||\', \'||name from stock_incoterms where active')
54 class sale_order(osv.osv):
56 _description = "Sale Order"
58 def copy(self, cr, uid, id, default=None, context=None):
68 'name': self.pool.get('ir.sequence').get(cr, uid, 'sale.order'),
70 return super(sale_order, self).copy(cr, uid, id, default, context=context)
72 def _amount_line_tax(self, cr, uid, line, context=None):
76 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']:
80 def _amount_all(self, cr, uid, ids, field_name, arg, context=None):
81 cur_obj = self.pool.get('res.currency')
85 for order in self.browse(cr, uid, ids, context=context):
87 'amount_untaxed': 0.0,
92 cur = order.pricelist_id.currency_id
93 for line in order.order_line:
94 val1 += line.price_subtotal
95 val += self._amount_line_tax(cr, uid, line, context=context)
96 res[order.id]['amount_tax'] = cur_obj.round(cr, uid, cur, val)
97 res[order.id]['amount_untaxed'] = cur_obj.round(cr, uid, cur, val1)
98 res[order.id]['amount_total'] = res[order.id]['amount_untaxed'] + res[order.id]['amount_tax']
102 def _picked_rate(self, cr, uid, ids, name, arg, context=None):
111 p.sale_id,sum(m.product_qty), mp.state as mp_state
115 stock_picking p on (p.id=m.picking_id)
117 procurement_order mp on (mp.move_id=m.id)
119 p.sale_id IN %s GROUP BY mp.state, p.sale_id''',(tuple(ids),))
120 for oid, nbr, mp_state in cr.fetchall():
121 if mp_state == 'cancel':
123 if mp_state == 'done':
124 res[oid][0] += nbr or 0.0
125 res[oid][1] += nbr or 0.0
127 res[oid][1] += nbr or 0.0
132 res[r] = 100.0 * res[r][0] / res[r][1]
133 for order in self.browse(cr, uid, ids, context=context):
135 res[order.id] = 100.0
138 def _invoiced_rate(self, cursor, user, ids, name, arg, context=None):
142 for sale in self.browse(cursor, user, ids, context=context):
147 for invoice in sale.invoice_ids:
148 if invoice.state not in ('draft', 'cancel'):
149 tot += invoice.amount_untaxed
152 res[sale.id] = min(100.0, tot * 100.0 / (sale.amount_untaxed or 1.00))
157 def _invoiced(self, cursor, user, ids, name, arg, context=None):
161 for sale in self.browse(cursor, user, ids, context=context):
163 for invoice in sale.invoice_ids:
164 if invoice.state != 'paid':
167 if not sale.invoice_ids:
171 def _invoiced_search(self, cursor, user, obj, name, args, context=None):
181 clause += 'AND inv.state = \'paid\''
183 clause += 'AND inv.state <> \'paid\''
185 cursor.execute('SELECT rel.order_id ' \
186 'FROM sale_order_invoice_rel AS rel, account_invoice AS inv ' \
187 'WHERE rel.invoice_id = inv.id ' + clause)
188 res = cursor.fetchall()
190 cursor.execute('SELECT sale.id ' \
191 'FROM sale_order AS sale ' \
192 'WHERE sale.id NOT IN ' \
193 '(SELECT rel.order_id ' \
194 'FROM sale_order_invoice_rel AS rel)')
195 res.extend(cursor.fetchall())
197 return [('id', '=', 0)]
198 return [('id', 'in', [x[0] for x in res])]
200 def _get_order(self, cr, uid, ids, context=None):
204 for line in self.pool.get('sale.order.line').browse(cr, uid, ids, context=context):
205 result[line.order_id.id] = True
209 'name': fields.char('Order Reference', size=64, required=True,
210 readonly=True, states={'draft': [('readonly', False)]}, select=True),
211 'shop_id': fields.many2one('sale.shop', 'Shop', required=True, readonly=True, states={'draft': [('readonly', False)]}),
212 'origin': fields.char('Source document', size=64, help="Reference of the document that generated this sale order request."),
213 'client_order_ref': fields.char('Customer Reference', size=64),
215 'state': fields.selection([
216 ('draft', 'Quotation'),
217 ('waiting_date', 'Waiting Schedule'),
218 ('manual', 'Manual In Progress'),
219 ('progress', 'In Progress'),
220 ('shipping_except', 'Shipping Exception'),
221 ('invoice_except', 'Invoice Exception'),
223 ('cancel', 'Cancelled')
224 ], '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 'Ordered Date'.", select=True),
225 'date_order': fields.date('Ordered Date', required=True, readonly=True, states={'draft': [('readonly', False)]}),
226 'create_date': fields.date('Creation Date', readonly=True, help="Date on which sale order is created"),
227 'date_confirm': fields.date('Confirmation Date', readonly=True, help="Date on which sale order is confirmed"),
228 'user_id': fields.many2one('res.users', 'Salesman', states={'draft': [('readonly', False)]}, select=True),
229 'partner_id': fields.many2one('res.partner', 'Customer', readonly=True, states={'draft': [('readonly', False)]}, required=True, change_default=True, select=True),
230 '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"),
231 '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."),
232 '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"),
234 'incoterm': fields.selection(_incoterm_get, 'Incoterm', size=3, help="Incoterm which stands for 'International Commercial terms' implies its a series of sales terms which are used in the commercial transaction"),
235 'picking_policy': fields.selection([('direct', 'Partial Delivery'), ('one', 'Complete Delivery')],
236 '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?"""),
237 'order_policy': fields.selection([
238 ('prepaid', 'Payment Before Delivery'),
239 ('manual', 'Shipping & Manual Invoice'),
240 ('postpaid', 'Invoice on Order After Delivery'),
241 ('picking', 'Invoice from Picking'),
242 ], 'Shipping Policy', required=True, readonly=True, states={'draft': [('readonly', False)]},
243 help="""The Shipping Policy is used to synchronise invoice and delivery operations.
244 - The 'Pay before delivery' choice will first generate the invoice and then generate the picking order after the payment of this invoice.
245 - 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.
246 - The 'Invoice on Order After Delivery' choice will generate the draft invoice based on sale order after all picking lists have been finished.
247 - The 'Invoice from the picking' choice is used to create an invoice during the picking process."""),
248 'pricelist_id': fields.many2one('product.pricelist', 'Pricelist', required=True, readonly=True, states={'draft': [('readonly', False)]}, help="Pricelist version for current sale order"),
249 'project_id': fields.many2one('account.analytic.account', 'Analytic Account', readonly=True, states={'draft': [('readonly', False)]}, help="The analytic account related to a sale order"),
251 'order_line': fields.one2many('sale.order.line', 'order_id', 'Order Lines', readonly=True, states={'draft': [('readonly', False)]}),
252 '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)."),
253 '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"),
254 'shipped': fields.boolean('Picked', readonly=True, help="It indicates that a picking has been done.It will set to True if the ordered quantities are available and the picking is done.If the ordered quantities are not available it generates a Purchase/Manufacturing order.Unless its Picking and Purchase/Manufacturing order are not in the done state it wont be set to True"),
255 'picked_rate': fields.function(_picked_rate, method=True, string='Picked', type='float'),
256 'invoiced_rate': fields.function(_invoiced_rate, method=True, string='Invoiced', type='float'),
257 'invoiced': fields.function(_invoiced, method=True, string='Paid',
258 fnct_search=_invoiced_search, type='boolean', help="It indicates that an invoice has been paid"),
259 'note': fields.text('Notes'),
261 'amount_untaxed': fields.function(_amount_all, method=True, digits_compute= dp.get_precision('Sale Price'), string='Untaxed Amount',
263 'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line'], 10),
264 'sale.order.line': (_get_order, ['price_unit', 'tax_id', 'discount', 'product_uom_qty'], 10),
266 multi='sums', help="The amount without tax"),
267 'amount_tax': fields.function(_amount_all, method=True, digits_compute= dp.get_precision('Sale Price'), string='Taxes',
269 'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line'], 10),
270 'sale.order.line': (_get_order, ['price_unit', 'tax_id', 'discount', 'product_uom_qty'], 10),
272 multi='sums', help="The tax amount"),
273 'amount_total': fields.function(_amount_all, method=True, digits_compute= dp.get_precision('Sale Price'), string='Total',
275 'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line'], 10),
276 'sale.order.line': (_get_order, ['price_unit', 'tax_id', 'discount', 'product_uom_qty'], 10),
278 multi='sums', help="The total amount"),
280 '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),
281 'payment_term': fields.many2one('account.payment.term', 'Payment Term'),
282 'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position'),
283 'company_id': fields.related('shop_id','company_id',type='many2one',relation='res.company',string='Company',store=True)
286 'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'sale.order', context=c),
287 'picking_policy': 'direct',
288 'date_order': time.strftime('%Y-%m-%d'),
289 'order_policy': 'manual',
291 'user_id': lambda obj, cr, uid, context: uid,
292 'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'sale.order'),
293 'invoice_quantity': 'order',
294 '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'],
295 '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'],
296 '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'],
297 # '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,
302 def unlink(self, cr, uid, ids, context=None):
305 sale_orders = self.read(cr, uid, ids, ['state'], context=context)
307 for s in sale_orders:
308 if s['state'] in ['draft', 'cancel']:
309 unlink_ids.append(s['id'])
311 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Sale Order(s) which are already confirmed !'))
312 return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
314 def onchange_shop_id(self, cr, uid, ids, shop_id):
317 shop = self.pool.get('sale.shop').browse(cr, uid, shop_id)
318 v['project_id'] = shop.project_id.id
319 # Que faire si le client a une pricelist a lui ?
320 if shop.pricelist_id.id:
321 v['pricelist_id'] = shop.pricelist_id.id
322 #v['payment_default_id']=shop.payment_default_id.id
325 def action_cancel_draft(self, cr, uid, ids, *args):
328 cr.execute('select id from sale_order_line where order_id IN %s and state=%s',(tuple(ids),'cancel'))
329 line_ids = map(lambda x: x[0], cr.fetchall())
330 self.write(cr, uid, ids, {'state': 'draft', 'invoice_ids': [], 'shipped': 0})
331 self.pool.get('sale.order.line').write(cr, uid, line_ids, {'invoiced': False, 'state': 'draft', 'invoice_lines': [(6, 0, [])]})
332 wf_service = netsvc.LocalService("workflow")
334 # Deleting the existing instance of workflow for SO
335 wf_service.trg_delete(uid, 'sale.order', inv_id, cr)
336 wf_service.trg_create(uid, 'sale.order', inv_id, cr)
337 for (id,name) in self.name_get(cr, uid, ids):
338 message = _('Sale order ') + " '" + name + "' "+ _("is in draft state")
339 self.log(cr, uid, id, message)
342 def onchange_partner_id(self, cr, uid, ids, part):
344 return {'value': {'partner_invoice_id': False, 'partner_shipping_id': False, 'partner_order_id': False, 'payment_term': False, 'fiscal_position': False}}
346 addr = self.pool.get('res.partner').address_get(cr, uid, [part], ['delivery', 'invoice', 'contact'])
347 part = self.pool.get('res.partner').browse(cr, uid, part)
348 pricelist = part.property_product_pricelist and part.property_product_pricelist.id or False
349 payment_term = part.property_payment_term and part.property_payment_term.id or False
350 fiscal_position = part.property_account_position and part.property_account_position.id or False
351 dedicated_salesman = part.user_id and part.user_id.id or uid
354 'partner_invoice_id': addr['invoice'],
355 'partner_order_id': addr['contact'],
356 'partner_shipping_id': addr['delivery'],
357 'payment_term': payment_term,
358 'fiscal_position': fiscal_position,
359 'user_id': dedicated_salesman,
363 val['pricelist_id'] = pricelist
365 return {'value': val}
367 def shipping_policy_change(self, cr, uid, ids, policy, context=None):
373 if policy == 'prepaid':
375 elif policy == 'picking':
376 inv_qty = 'procurement'
377 return {'value': {'invoice_quantity': inv_qty}}
379 def write(self, cr, uid, ids, vals, context=None):
382 if 'order_policy' in vals:
383 if vals['order_policy'] == 'prepaid':
384 vals.update({'invoice_quantity': 'order'})
385 elif vals['order_policy'] == 'picking':
386 vals.update({'invoice_quantity': 'procurement'})
387 return super(sale_order, self).write(cr, uid, ids, vals, context=context)
389 def create(self, cr, uid, vals, context=None):
392 if 'order_policy' in vals:
393 if vals['order_policy'] == 'prepaid':
394 vals.update({'invoice_quantity': 'order'})
395 if vals['order_policy'] == 'picking':
396 vals.update({'invoice_quantity': 'procurement'})
397 return super(sale_order, self).create(cr, uid, vals, context=context)
399 def button_dummy(self, cr, uid, ids, context=None):
404 #FIXME: the method should return the list of invoices created (invoice_ids)
405 # and not the id of the last invoice created (res). The problem is that we
406 # cannot change it directly since the method is called by the sale order
407 # workflow and I suppose it expects a single id...
408 def _inv_get(self, cr, uid, order, context=None):
413 def _make_invoice(self, cr, uid, order, lines, context=None):
414 journal_obj = self.pool.get('account.journal')
415 inv_obj = self.pool.get('account.invoice')
420 journal_ids = journal_obj.search(cr, uid, [('type', '=','sale'),('company_id', '=', order.company_id.id)], limit=1)
422 raise osv.except_osv(_('Error !'),
423 _('There is no sale journal defined for this company: "%s" (id:%d)') % (order.company_id.name, order.company_id.id))
424 a = order.partner_id.property_account_receivable.id
425 if order.payment_term:
426 pay_term = order.payment_term.id
429 for preinv in order.invoice_ids:
430 if preinv.state not in ('cancel',):
431 for preline in preinv.invoice_line:
432 inv_line_id = self.pool.get('account.invoice.line').copy(cr, uid, preline.id, {'invoice_id': False, 'price_unit': -preline.price_unit})
433 lines.append(inv_line_id)
435 'name': order.client_order_ref or order.name,
436 'origin': order.name,
437 'type': 'out_invoice',
438 'reference': "P%dSO%d" % (order.partner_id.id, order.id),
440 'partner_id': order.partner_id.id,
441 'journal_id': journal_ids[0],
442 'address_invoice_id': order.partner_invoice_id.id,
443 'address_contact_id': order.partner_order_id.id,
444 'invoice_line': [(6, 0, lines)],
445 'currency_id': order.pricelist_id.currency_id.id,
446 'comment': order.note,
447 'payment_term': pay_term,
448 'fiscal_position': order.fiscal_position.id or order.partner_id.property_account_position.id,
449 'date_invoice' : context.get('date_invoice',False),
450 'company_id' : order.company_id.id,
451 'user_id':order.user_id and order.user_id.id or False
453 inv.update(self._inv_get(cr, uid, order))
454 inv_id = inv_obj.create(cr, uid, inv, context=context)
455 data = inv_obj.onchange_payment_term_date_invoice(cr, uid, [inv_id], pay_term, time.strftime('%Y-%m-%d'))
456 if data.get('value', False):
457 inv_obj.write(cr, uid, [inv_id], data['value'], context=context)
458 inv_obj.button_compute(cr, uid, [inv_id])
461 def action_invoice_create(self, cr, uid, ids, grouped=False, states=['confirmed', 'done', 'exception'], date_inv = False, context=None):
465 picking_obj = self.pool.get('stock.picking')
466 invoice = self.pool.get('account.invoice')
469 # If date was specified, use it as date invoiced, usefull when invoices are generated this month and put the
470 # last day of the last month as invoice date
472 context['date_inv'] = date_inv
473 for o in self.browse(cr, uid, ids):
475 for line in o.order_line:
476 if (line.state in states) and not line.invoiced:
477 lines.append(line.id)
478 created_lines = self.pool.get('sale.order.line').invoice_line_create(cr, uid, lines)
480 invoices.setdefault(o.partner_id.id, []).append((o, created_lines))
482 for o in self.browse(cr, uid, ids):
483 for i in o.invoice_ids:
484 if i.state == 'draft':
486 for val in invoices.values():
488 res = self._make_invoice(cr, uid, val[0][0], reduce(lambda x,y: x + y, [l for o,l in val], []), context=context)
491 invoice_ref += o.name + '|'
492 self.write(cr, uid, [o.id], {'state': 'progress'})
493 if o.order_policy == 'picking':
494 picking_obj.write(cr, uid, map(lambda x: x.id, o.picking_ids), {'invoice_state': 'invoiced'})
495 cr.execute('insert into sale_order_invoice_rel (order_id,invoice_id) values (%s,%s)', (o.id, res))
496 invoice.write(cr, uid, [res], {'origin': invoice_ref, 'name': invoice_ref})
498 for order, il in val:
499 res = self._make_invoice(cr, uid, order, il, context=context)
500 invoice_ids.append(res)
501 self.write(cr, uid, [order.id], {'state': 'progress'})
502 if order.order_policy == 'picking':
503 picking_obj.write(cr, uid, map(lambda x: x.id, order.picking_ids), {'invoice_state': 'invoiced'})
504 cr.execute('insert into sale_order_invoice_rel (order_id,invoice_id) values (%s,%s)', (order.id, res))
507 def action_invoice_cancel(self, cr, uid, ids, context=None):
510 for sale in self.browse(cr, uid, ids, context=context):
511 for line in sale.order_line:
513 for iline in line.invoice_lines:
514 if iline.invoice_id and iline.invoice_id.state == 'cancel':
518 self.pool.get('sale.order.line').write(cr, uid, [line.id], {'invoiced': invoiced})
519 self.write(cr, uid, ids, {'state': 'invoice_except', 'invoice_ids': False})
522 def action_invoice_end(self, cr, uid, ids, context=None):
526 for order in self.browse(cr, uid, ids, context=context):
527 for line in order.order_line:
528 if line.state == 'exception':
529 self.pool.get('sale.order.line').write(cr, uid, [line.id], {'state': 'confirmed'}, context=context)
531 if order.state == 'invoice_except':
532 self.write(cr, uid, [order.id], {'state' : 'progress'}, context=context)
536 def action_cancel(self, cr, uid, ids, context=None):
540 sale_order_line_obj = self.pool.get('sale.order.line')
541 for sale in self.browse(cr, uid, ids, context=context):
542 for pick in sale.picking_ids:
543 if pick.state not in ('draft', 'cancel'):
544 raise osv.except_osv(
545 _('Could not cancel sale order !'),
546 _('You must first cancel all picking attached to this sale order.'))
547 for r in self.read(cr, uid, ids, ['picking_ids']):
548 for pick in r['picking_ids']:
549 wf_service = netsvc.LocalService("workflow")
550 wf_service.trg_validate(uid, 'stock.picking', pick, 'button_cancel', cr)
551 for inv in sale.invoice_ids:
552 if inv.state not in ('draft', 'cancel'):
553 raise osv.except_osv(
554 _('Could not cancel this sale order !'),
555 _('You must first cancel all invoices attached to this sale order.'))
556 for r in self.read(cr, uid, ids, ['invoice_ids']):
557 for inv in r['invoice_ids']:
558 wf_service = netsvc.LocalService("workflow")
559 wf_service.trg_validate(uid, 'account.invoice', inv, 'invoice_cancel', cr)
560 sale_order_line_obj.write(cr, uid, [l.id for l in sale.order_line],
562 self.write(cr, uid, ids, {'state': 'cancel'})
563 message = _('Sale order') + " '" + sale.name + "' "+ _("created on")+" '" +sale.create_date + _(" is cancelled")
564 self.log(cr, uid, id, message)
567 def action_wait(self, cr, uid, ids, *args):
569 product_obj = self.pool.get('product.product')
570 for o in self.browse(cr, uid, ids):
571 if (o.order_policy == 'manual'):
572 self.write(cr, uid, [o.id], {'state': 'manual', 'date_confirm': time.strftime('%Y-%m-%d')})
574 self.write(cr, uid, [o.id], {'state': 'progress', 'date_confirm': time.strftime('%Y-%m-%d')})
575 self.pool.get('sale.order.line').button_confirm(cr, uid, [x.id for x in o.order_line])
576 for line in o.order_line:
577 product.append(line.product_id.default_code)
578 params = ', '.join(map(lambda x : str(x),product))
579 message = _('Sale order ') + " '" + o.name + "' "+ _("created on")+" '" +o.create_date + "' "+_("for")+" '" +params + "' "+_("is confirmed")
580 self.log(cr, uid, id, message)
583 def procurement_lines_get(self, cr, uid, ids, *args):
585 for order in self.browse(cr, uid, ids, context={}):
586 for line in order.order_line:
587 if line.procurement_id:
588 res.append(line.procurement_id.id)
591 # if mode == 'finished':
592 # returns True if all lines are done, False otherwise
593 # if mode == 'canceled':
594 # returns True if there is at least one canceled line, False otherwise
595 def test_state(self, cr, uid, ids, mode, *args):
596 assert mode in ('finished', 'canceled'), _("invalid mode for test_state")
601 write_cancel_ids = []
602 for order in self.browse(cr, uid, ids, context={}):
603 for line in order.order_line:
604 if (not line.procurement_id) or (line.procurement_id.state=='done'):
605 if line.state != 'done':
606 write_done_ids.append(line.id)
609 if line.procurement_id:
610 if (line.procurement_id.state == 'cancel'):
612 if line.state != 'exception':
613 write_cancel_ids.append(line.id)
617 self.pool.get('sale.order.line').write(cr, uid, write_done_ids, {'state': 'done'})
619 self.pool.get('sale.order.line').write(cr, uid, write_cancel_ids, {'state': 'exception'})
621 if mode == 'finished':
623 elif mode == 'canceled':
629 def action_ship_create(self, cr, uid, ids, *args):
631 company = self.pool.get('res.users').browse(cr, uid, uid).company_id
632 for order in self.browse(cr, uid, ids, context={}):
634 output_id = order.shop_id.warehouse_id.lot_output_id.id
636 for line in order.order_line:
638 date_planned = datetime.now() + relativedelta(days=line.delay or 0.0)
639 date_planned = (date_planned - relativedelta(company.security_lead)).strftime('%Y-%m-%d %H:%M:%S')
640 if line.state == 'done':
643 if line.product_id and line.product_id.product_tmpl_id.type in ('product', 'consu'):
644 location_id = order.shop_id.warehouse_id.lot_stock_id.id
646 loc_dest_id = order.partner_id.property_stock_customer.id
647 pick_name = self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.out')
648 picking_id = self.pool.get('stock.picking').create(cr, uid, {
650 'origin': order.name,
653 'move_type': order.picking_policy,
655 'address_id': order.partner_shipping_id.id,
657 'invoice_state': (order.order_policy=='picking' and '2binvoiced') or 'none',
658 'company_id': order.company_id.id,
661 move_id = self.pool.get('stock.move').create(cr, uid, {
662 'name': line.name[:64],
663 'picking_id': picking_id,
664 'product_id': line.product_id.id,
665 'date_planned': date_planned,
666 'date_expected': date_planned,
667 'product_qty': line.product_uom_qty,
668 'product_uom': line.product_uom.id,
669 'product_uos_qty': line.product_uos_qty,
670 'product_uos': (line.product_uos and line.product_uos.id)\
671 or line.product_uom.id,
672 'product_packaging': line.product_packaging.id,
673 'address_id': line.address_allotment_id.id or order.partner_shipping_id.id,
674 'location_id': location_id,
675 'location_dest_id': output_id,
676 'sale_line_id': line.id,
677 'tracking_id': False,
681 'company_id': order.company_id.id,
684 proc_id = self.pool.get('procurement.order').create(cr, uid, {
686 'origin': order.name,
687 'date_planned': date_planned,
688 'product_id': line.product_id.id,
689 'product_qty': line.product_uom_qty,
690 'product_uom': line.product_uom.id,
691 'product_uos_qty': (line.product_uos and line.product_uos_qty)\
692 or line.product_uom_qty,
693 'product_uos': (line.product_uos and line.product_uos.id)\
694 or line.product_uom.id,
695 'location_id': order.shop_id.warehouse_id.lot_stock_id.id,
696 'procure_method': line.type,
698 'property_ids': [(6, 0, [x.id for x in line.property_ids])],
699 'company_id': order.company_id.id,
701 proc_ids.append(proc_id)
702 self.pool.get('sale.order.line').write(cr, uid, [line.id], {'procurement_id': proc_id})
706 wf_service = netsvc.LocalService("workflow")
707 wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
709 for proc_id in proc_ids:
710 wf_service = netsvc.LocalService("workflow")
711 wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_confirm', cr)
713 if order.state == 'shipping_except':
714 val['state'] = 'progress'
716 if (order.order_policy == 'manual'):
717 for line in order.order_line:
718 if (not line.invoiced) and (line.state not in ('cancel', 'draft')):
719 val['state'] = 'manual'
721 self.write(cr, uid, [order.id], val)
725 def action_ship_end(self, cr, uid, ids, context=None):
728 for order in self.browse(cr, uid, ids, context=context):
729 val = {'shipped': True}
730 if order.state == 'shipping_except':
731 val['state'] = 'progress'
732 if (order.order_policy == 'manual'):
733 for line in order.order_line:
734 if (not line.invoiced) and (line.state not in ('cancel', 'draft')):
735 val['state'] = 'manual'
737 for line in order.order_line:
739 if line.state == 'exception':
740 towrite.append(line.id)
742 self.pool.get('sale.order.line').write(cr, uid, towrite, {'state': 'done'}, context=context)
743 self.write(cr, uid, [order.id], val)
746 def _log_event(self, cr, uid, ids, factor=0.7, name='Open Order'):
747 invs = self.read(cr, uid, ids, ['date_order', 'partner_id', 'amount_untaxed'])
749 part = inv['partner_id'] and inv['partner_id'][0]
750 pr = inv['amount_untaxed'] or 0.0
751 partnertype = 'customer'
754 'name': 'Order: '+name,
756 'description': 'Order '+str(inv['id']),
759 'date': time.strftime('%Y-%m-%d'),
762 'partner_type': partnertype,
764 'planned_revenue': pr,
768 self.pool.get('res.partner.event').create(cr, uid, event)
770 def has_stockable_products(self, cr, uid, ids, *args):
771 for order in self.browse(cr, uid, ids):
772 for order_line in order.order_line:
773 if order_line.product_id and order_line.product_id.product_tmpl_id.type in ('product', 'consu'):
778 # TODO add a field price_unit_uos
779 # - update it on change product and unit price
780 # - use it in report if there is a uos
781 class sale_order_line(osv.osv):
782 def _amount_line(self, cr, uid, ids, field_name, arg, context=None):
783 tax_obj = self.pool.get('account.tax')
784 cur_obj = self.pool.get('res.currency')
786 context = context or {}
787 for line in self.browse(cr, uid, ids, context=context):
788 price = line.price_unit * line.product_uom_qty * (1 - (line.discount or 0.0) / 100.0)
789 taxes = tax_obj.compute_all(cr, uid, line.tax_id, line.price_unit, line.product_uom_qty)
790 cur = line.order_id.pricelist_id.currency_id
791 res[line.id] = cur_obj.round(cr, uid, cur, taxes['total'])
794 def _number_packages(self, cr, uid, ids, field_name, arg, context=None):
798 for line in self.browse(cr, uid, ids, context=context):
800 res[line.id] = int((line.product_uom_qty+line.product_packaging.qty-0.0001) / line.product_packaging.qty)
805 _name = 'sale.order.line'
806 _description = 'Sale Order Line'
808 'order_id': fields.many2one('sale.order', 'Order Reference', required=True, ondelete='cascade', select=True, readonly=True, states={'draft':[('readonly',False)]}),
809 'name': fields.char('Description', size=256, required=True, select=True, readonly=True, states={'draft':[('readonly',False)]}),
810 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of sale order lines."),
811 '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", readonly=True, states={'draft':[('readonly',False)]}),
812 'product_id': fields.many2one('product.product', 'Product', domain=[('sale_ok', '=', True)], change_default=True),
813 'invoice_lines': fields.many2many('account.invoice.line', 'sale_order_line_invoice_rel', 'order_line_id', 'invoice_id', 'Invoice Lines', readonly=True),
814 'invoiced': fields.boolean('Invoiced', readonly=True),
815 'procurement_id': fields.many2one('procurement.order', 'Procurement'),
816 'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Sale Price'), readonly=True, states={'draft':[('readonly',False)]}),
817 'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal', digits_compute= dp.get_precision('Sale Price')),
818 'tax_id': fields.many2many('account.tax', 'sale_order_tax', 'order_line_id', 'tax_id', 'Taxes', readonly=True, states={'draft':[('readonly',False)]}),
819 'type': fields.selection([('make_to_stock', 'from stock'), ('make_to_order', 'on order')], 'Procurement Method', required=True, readonly=True, states={'draft':[('readonly',False)]}),
820 'property_ids': fields.many2many('mrp.property', 'sale_order_line_property_rel', 'order_id', 'property_id', 'Properties', readonly=True, states={'draft':[('readonly',False)]}),
821 'address_allotment_id': fields.many2one('res.partner.address', 'Allotment Partner'),
822 'product_uom_qty': fields.float('Quantity (UoM)', digits=(16, 2), required=True, readonly=True, states={'draft':[('readonly',False)]}),
823 'product_uom': fields.many2one('product.uom', 'Unit of Measure ', required=True, readonly=True, states={'draft':[('readonly',False)]}),
824 'product_uos_qty': fields.float('Quantity (UoS)', readonly=True, states={'draft':[('readonly',False)]}),
825 'product_uos': fields.many2one('product.uom', 'Product UoS'),
826 'product_packaging': fields.many2one('product.packaging', 'Packaging'),
827 'move_ids': fields.one2many('stock.move', 'sale_line_id', 'Inventory Moves', readonly=True),
828 'discount': fields.float('Discount (%)', digits=(16, 2), readonly=True, states={'draft':[('readonly',False)]}),
829 'number_packages': fields.function(_number_packages, method=True, type='integer', string='Number Packages'),
830 'notes': fields.text('Notes'),
831 'th_weight': fields.float('Weight', readonly=True, states={'draft':[('readonly',False)]}),
832 'state': fields.selection([('draft', 'Draft'),('confirmed', 'Confirmed'),('done', 'Done'),('cancel', 'Cancelled'),('exception', 'Exception'),('invoiced','Invoiced')], 'State', required=True, readonly=True,
833 help=' * The \'Draft\' state is set automatically when sale order in draft state. \
834 \n* The \'Confirmed\' state is set automatically when sale order in confirm state. \
835 \n* The \'Exception\' state is set automatically when sale order is set as exception. \
836 \n* The \'Done\' state is set automatically when sale order is set as done. \
837 \n* The \'Cancelled\' state is set automatically when user cancel sale order\
838 \n* The \'Invoiced\' state is set automatically when user creates sale order line invoice.'),
839 'order_partner_id': fields.related('order_id', 'partner_id', type='many2one', relation='res.partner', string='Customer'),
840 'salesman_id':fields.related('order_id', 'user_id', type='many2one', relation='res.users', string='Salesman'),
841 'company_id': fields.related('order_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True, states={'draft':[('readonly',False)]}),
843 _order = 'sequence, id'
847 'product_uom_qty': 1,
848 'product_uos_qty': 1,
852 'type': 'make_to_stock',
853 'product_packaging': False
856 def create_sale_order_line_invoice(self, cr, uid, ids, context):
857 line_make_invoice_obj = self.pool.get("sale.order.line.make.invoice")
858 context.update({'active_ids' : ids,'active_id' : ids})
859 invoice_id = line_make_invoice_obj.make_invoices(cr, uid, ids, context)
860 return self.write(cr, uid, ids, {'state': 'invoiced'})
862 def invoice_line_create(self, cr, uid, ids, context=None):
866 def _get_line_qty(line):
867 if (line.order_id.invoice_quantity=='order') or not line.procurement_id:
869 return line.product_uos_qty or 0.0
870 return line.product_uom_qty
872 return self.pool.get('procurement.order').quantity_get(cr, uid,
873 line.procurement_id.id, context=context)
875 def _get_line_uom(line):
876 if (line.order_id.invoice_quantity=='order') or not line.procurement_id:
878 return line.product_uos.id
879 return line.product_uom.id
881 return self.pool.get('procurement.order').uom_get(cr, uid,
882 line.procurement_id.id, context=context)
886 for line in self.browse(cr, uid, ids, context=context):
887 if not line.invoiced:
889 a = line.product_id.product_tmpl_id.property_account_income.id
891 a = line.product_id.categ_id.property_account_income_categ.id
893 raise osv.except_osv(_('Error !'),
894 _('There is no income account defined ' \
895 'for this product: "%s" (id:%d)') % \
896 (line.product_id.name, line.product_id.id,))
898 a = self.pool.get('ir.property').get(cr, uid,
899 'property_account_income_categ', 'product.category',
901 uosqty = _get_line_qty(line)
902 uos_id = _get_line_uom(line)
905 pu = round(line.price_unit * line.product_uom_qty / uosqty,
906 self.pool.get('decimal.precision').precision_get(cr, uid, 'Sale Price'))
907 fpos = line.order_id.fiscal_position or False
908 a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
910 raise osv.except_osv(_('Error !'),
911 _('There is no income category account defined in default Properties for Product Category or Fiscal Position is not defined !'))
912 inv_id = self.pool.get('account.invoice.line').create(cr, uid, {
914 'origin': line.order_id.name,
918 'discount': line.discount,
920 'product_id': line.product_id.id or False,
921 'invoice_line_tax_id': [(6, 0, [x.id for x in line.tax_id])],
923 'account_analytic_id': line.order_id.project_id and line.order_id.project_id.id or False,
925 cr.execute('insert into sale_order_line_invoice_rel (order_line_id,invoice_id) values (%s,%s)', (line.id, inv_id))
926 self.write(cr, uid, [line.id], {'invoiced': True})
928 sales[line.order_id.id] = True
929 create_ids.append(inv_id)
930 # Trigger workflow events
931 wf_service = netsvc.LocalService("workflow")
932 for sid in sales.keys():
933 wf_service.trg_write(uid, 'sale.order', sid, cr)
936 def button_cancel(self, cr, uid, ids, context=None):
939 for line in self.browse(cr, uid, ids, context=context):
941 raise osv.except_osv(_('Invalid action !'), _('You cannot cancel a sale order line that has already been invoiced !'))
942 if line.order_id.picking_ids:
943 raise osv.except_osv(
944 _('Could not cancel sale order line!'),
945 _('You must first cancel stock move attached to this sale order line.'))
946 message = _('Sale order line') + " '" + line.name + "' "+_("is cancelled")
947 self.log(cr, uid, id, message)
948 return self.write(cr, uid, ids, {'state': 'cancel'})
950 def button_confirm(self, cr, uid, ids, context=None):
953 for (id,name) in self.name_get(cr, uid, ids):
954 message = _('Sale order line') + " '" + name + "' "+ _("is confirmed")
955 self.log(cr, uid, id, message)
956 return self.write(cr, uid, ids, {'state': 'confirmed'})
958 def button_done(self, cr, uid, ids, context=None):
961 wf_service = netsvc.LocalService("workflow")
962 res = self.write(cr, uid, ids, {'state': 'done'})
963 for line in self.browse(cr, uid, ids, context=context):
964 wf_service.trg_write(uid, 'sale.order', line.order_id.id, cr)
965 message = _('Sale order line') + " '" + line.name + "' "+_("is done")
966 self.log(cr, uid, id, message)
969 def uos_change(self, cr, uid, ids, product_uos, product_uos_qty=0, product_id=None):
970 product_obj = self.pool.get('product.product')
972 return {'value': {'product_uom': product_uos,
973 'product_uom_qty': product_uos_qty}, 'domain': {}}
975 product = product_obj.browse(cr, uid, product_id)
977 'product_uom': product.uom_id.id,
979 # FIXME must depend on uos/uom of the product and not only of the coeff.
982 'product_uom_qty': product_uos_qty / product.uos_coeff,
983 'th_weight': product_uos_qty / product.uos_coeff * product.weight
985 except ZeroDivisionError:
987 return {'value': value}
989 def copy_data(self, cr, uid, id, default=None, context=None):
994 default.update({'state': 'draft', 'move_ids': [], 'invoiced': False, 'invoice_lines': []})
995 return super(sale_order_line, self).copy_data(cr, uid, id, default, context=context)
997 def product_id_change(self, cr, uid, ids, pricelist, product, qty=0,
998 uom=False, qty_uos=0, uos=False, name='', partner_id=False,
999 lang=False, update_tax=True, date_order=False, packaging=False, fiscal_position=False, flag=False):
1001 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.'))
1003 product_uom_obj = self.pool.get('product.uom')
1004 partner_obj = self.pool.get('res.partner')
1005 product_obj = self.pool.get('product.product')
1007 lang = partner_obj.browse(cr, uid, partner_id).lang
1008 context = {'lang': lang, 'partner_id': partner_id}
1011 return {'value': {'th_weight': 0, 'product_packaging': False,
1012 'product_uos_qty': qty}, 'domain': {'product_uom': [],
1016 date_order = time.strftime('%Y-%m-%d')
1019 product_obj = product_obj.browse(cr, uid, product, context=context)
1020 if not packaging and product_obj.packaging:
1021 packaging = product_obj.packaging[0].id
1022 result['product_packaging'] = packaging
1025 default_uom = product_obj.uom_id and product_obj.uom_id.id
1026 pack = self.pool.get('product.packaging').browse(cr, uid, packaging, context=context)
1027 q = product_uom_obj._compute_qty(cr, uid, uom, pack.qty, default_uom)
1028 # qty = qty - qty % q + q
1029 if qty and (q and not (qty % q) == 0):
1033 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)
1034 warn_msg = warn_msg + "\n\n" + _("EAN: ") + str(ean) + _(" Quantity: ") + str(qty_pack) + _(" Type of ul: ") + str(type_ul.name)
1036 'title': _('Picking Information !'),
1039 result['product_uom_qty'] = qty
1043 uom2 = product_uom_obj.browse(cr, uid, uom)
1044 if product_obj.uom_id.category_id.id != uom2.category_id.id:
1048 if product_obj.uos_id:
1049 uos2 = product_uom_obj.browse(cr, uid, uos)
1050 if product_obj.uos_id.category_id.id != uos2.category_id.id:
1054 if product_obj.description_sale:
1055 result['notes'] = product_obj.description_sale
1056 fpos = fiscal_position and self.pool.get('account.fiscal.position').browse(cr, uid, fiscal_position) or False
1057 if update_tax: #The quantity only have changed
1058 result['delay'] = (product_obj.sale_delay or 0.0)
1059 partner = partner_obj.browse(cr, uid, partner_id)
1060 result['tax_id'] = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, product_obj.taxes_id)
1061 result.update({'type': product_obj.procure_method})
1064 result['name'] = self.pool.get('product.product').name_get(cr, uid, [product_obj.id], context=context)[0][1]
1066 if (not uom) and (not uos):
1067 result['product_uom'] = product_obj.uom_id.id
1068 if product_obj.uos_id:
1069 result['product_uos'] = product_obj.uos_id.id
1070 result['product_uos_qty'] = qty * product_obj.uos_coeff
1071 uos_category_id = product_obj.uos_id.category_id.id
1073 result['product_uos'] = False
1074 result['product_uos_qty'] = qty
1075 uos_category_id = False
1076 result['th_weight'] = qty * product_obj.weight
1077 domain = {'product_uom':
1078 [('category_id', '=', product_obj.uom_id.category_id.id)],
1080 [('category_id', '=', uos_category_id)]}
1082 elif uos and not uom: # only happens if uom is False
1083 result['product_uom'] = product_obj.uom_id and product_obj.uom_id.id
1084 result['product_uom_qty'] = qty_uos / product_obj.uos_coeff
1085 result['th_weight'] = result['product_uom_qty'] * product_obj.weight
1086 elif uom: # whether uos is set or not
1087 default_uom = product_obj.uom_id and product_obj.uom_id.id
1088 q = product_uom_obj._compute_qty(cr, uid, uom, qty, default_uom)
1089 if product_obj.uos_id:
1090 result['product_uos'] = product_obj.uos_id.id
1091 result['product_uos_qty'] = qty * product_obj.uos_coeff
1093 result['product_uos'] = False
1094 result['product_uos_qty'] = qty
1095 result['th_weight'] = q * product_obj.weight # Round the quantity up
1098 uom2 = product_obj.uom_id
1099 if (product_obj.type=='product') and (product_obj.virtual_available * uom2.factor < qty * product_obj.uom_id.factor) \
1100 and (product_obj.procure_method=='make_to_stock'):
1102 'title': _('Not enough stock !'),
1103 'message': _('You plan to sell %.2f %s but you only have %.2f %s available !\nThe real stock is %.2f %s. (without reservations)') %
1104 (qty, uom2 and uom2.name or product_obj.uom_id.name,
1105 max(0,product_obj.virtual_available), product_obj.uom_id.name,
1106 max(0,product_obj.qty_available), product_obj.uom_id.name)
1114 'title': 'No Pricelist !',
1116 'You have to select a pricelist or a customer in the sale form !\n'
1117 'Please set one before choosing a product.'
1120 price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist],
1121 product, qty or 1.0, partner_id, {
1127 'title': 'No valid pricelist line found !',
1129 "Couldn't find a pricelist line matching this product and quantity.\n"
1130 "You have to change either the product, the quantity or the pricelist."
1133 result.update({'price_unit': price})
1134 return {'value': result, 'domain': domain, 'warning': warning}
1136 def product_uom_change(self, cursor, user, ids, pricelist, product, qty=0,
1137 uom=False, qty_uos=0, uos=False, name='', partner_id=False,
1138 lang=False, update_tax=True, date_order=False):
1139 res = self.product_id_change(cursor, user, ids, pricelist, product,
1140 qty=qty, uom=uom, qty_uos=qty_uos, uos=uos, name=name,
1141 partner_id=partner_id, lang=lang, update_tax=update_tax,
1142 date_order=date_order)
1143 if 'product_uom' in res['value']:
1144 del res['value']['product_uom']
1146 res['value']['price_unit'] = 0.0
1149 def unlink(self, cr, uid, ids, context=None):
1152 """Allows to delete sale order lines in draft,cancel states"""
1153 for rec in self.browse(cr, uid, ids, context=context):
1154 if rec.state not in ['draft', 'cancel']:
1155 raise osv.except_osv(_('Invalid action !'), _('Cannot delete a sale order line which is %s !')%(rec.state,))
1156 return super(sale_order_line, self).unlink(cr, uid, ids, context=context)
1161 class sale_config_picking_policy(osv.osv_memory):
1162 _name = 'sale.config.picking_policy'
1163 _inherit = 'res.config'
1166 'name': fields.char('Name', size=64),
1167 'picking_policy': fields.selection([
1168 ('direct', 'Direct Delivery'),
1169 ('one', 'All at Once')
1170 ], 'Picking Default Policy', required=True),
1171 'order_policy': fields.selection([
1172 ('manual', 'Invoice Based on Sales Orders'),
1173 ('picking', 'Invoice Based on Deliveries'),
1174 ], 'Shipping Default Policy', required=True,
1175 help="You can generate invoices based on sales orders or based on shippings."),
1176 'step': fields.selection([
1177 ('one', 'Delivery Order Only'),
1178 ('two', 'Picking List & Delivery Order')
1179 ], 'Steps To Deliver a Sale Order', required=True,
1180 help="By default, Open ERP is able to manage complex routing and paths "\
1181 "of products in your warehouse and partner locations. This will configure "\
1182 "the most common and simple methods to deliver products to the customer "\
1183 "in one or two operations by the worker.")
1186 'picking_policy': lambda *a: 'direct',
1187 'order_policy': lambda *a: 'manual',
1188 'step': lambda *a: 'one'
1191 def execute(self, cr, uid, ids, context=None):
1194 for o in self.browse(cr, uid, ids, context=context):
1195 ir_values_obj = self.pool.get('ir.values')
1196 ir_values_obj.set(cr, uid, 'default', False, 'picking_policy', ['sale.order'], o.picking_policy)
1197 ir_values_obj.set(cr, uid, 'default', False, 'order_policy', ['sale.order'], o.order_policy)
1200 md = self.pool.get('ir.model.data')
1201 group_id = md._get_id(cr, uid, 'base', 'group_no_one')
1202 group_id = md.browse(cr, uid, group_id, context=context).res_id
1203 menu_id = md._get_id(cr, uid, 'stock', 'menu_action_picking_tree_delivery')
1204 menu_id = md.browse(cr, uid, menu_id, context=context).res_id
1205 self.pool.get('ir.ui.menu').write(cr, uid, [menu_id], {'groups_id': [(6, 0, [group_id])]})
1207 location_id = md._get_id(cr, uid, 'stock', 'stock_location_output')
1208 location_id = md.browse(cr, uid, location_id, context=context).res_id
1209 self.pool.get('stock.location').write(cr, uid, [location_id], {'chained_auto_packing': 'transparent'})
1210 sale_config_picking_policy()
1212 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: