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, timedelta
24 from dateutil.relativedelta import relativedelta
27 from osv import fields, osv
28 from tools import config
29 from tools.translate import _
30 import decimal_precision as dp
32 class sale_shop(osv.osv):
34 _description = "Sale Shop"
36 'name': fields.char('Shop Name', size=64, required=True),
37 'payment_default_id': fields.many2one('account.payment.term', 'Default Payment Term', required=True),
38 'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse'),
39 'pricelist_id': fields.many2one('product.pricelist', 'Pricelist'),
40 'project_id': fields.many2one('account.analytic.account', 'Analytic Account'),
41 'company_id': fields.many2one('res.company', 'Company'),
46 def _incoterm_get(self, cr, uid, context=None):
49 cr.execute('select code, code||\', \'||name from stock_incoterms where active')
52 class sale_order(osv.osv):
54 _description = "Sale Order"
56 def copy(self, cr, uid, id, default=None, context=None):
66 'name': self.pool.get('ir.sequence').get(cr, uid, 'sale.order'),
68 return super(sale_order, self).copy(cr, uid, id, default, context=context)
70 def _amount_line_tax(self, cr, uid, line, context=None):
74 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']:
78 def _amount_all(self, cr, uid, ids, field_name, arg, context=None):
79 cur_obj = self.pool.get('res.currency')
83 for order in self.browse(cr, uid, ids, context=context):
85 'amount_untaxed': 0.0,
90 cur = order.pricelist_id.currency_id
91 for line in order.order_line:
92 val1 += line.price_subtotal
93 val += self._amount_line_tax(cr, uid, line, context=context)
94 res[order.id]['amount_tax'] = cur_obj.round(cr, uid, cur, val)
95 res[order.id]['amount_untaxed'] = cur_obj.round(cr, uid, cur, val1)
96 res[order.id]['amount_total'] = res[order.id]['amount_untaxed'] + res[order.id]['amount_tax']
100 def _picked_rate(self, cr, uid, ids, name, arg, context=None):
109 p.sale_id,sum(m.product_qty), mp.state as mp_state
113 stock_picking p on (p.id=m.picking_id)
115 procurement_order mp on (mp.move_id=m.id)
117 p.sale_id IN %s GROUP BY mp.state, p.sale_id''',(tuple(ids),))
118 for oid, nbr, mp_state in cr.fetchall():
119 if mp_state == 'cancel':
121 if mp_state == 'done':
122 res[oid][0] += nbr or 0.0
123 res[oid][1] += nbr or 0.0
125 res[oid][1] += nbr or 0.0
130 res[r] = 100.0 * res[r][0] / res[r][1]
131 for order in self.browse(cr, uid, ids, context=context):
133 res[order.id] = 100.0
136 def _invoiced_rate(self, cursor, user, ids, name, arg, context=None):
140 for sale in self.browse(cursor, user, ids, context=context):
145 for invoice in sale.invoice_ids:
146 if invoice.state not in ('draft', 'cancel'):
147 tot += invoice.amount_untaxed
149 res[sale.id] = min(100.0, tot * 100.0 / (sale.amount_untaxed or 1.00))
154 def _invoiced(self, cursor, user, ids, name, arg, context=None):
158 for sale in self.browse(cursor, user, ids, context=context):
160 for invoice in sale.invoice_ids:
161 if invoice.state != 'paid':
164 if not sale.invoice_ids:
168 def _invoiced_search(self, cursor, user, obj, name, args, context=None):
178 clause += 'AND inv.state = \'paid\''
180 clause += 'AND inv.state <> \'paid\''
182 cursor.execute('SELECT rel.order_id ' \
183 'FROM sale_order_invoice_rel AS rel, account_invoice AS inv ' \
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)')
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.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"),
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 Picking'),
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', 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('Picked', readonly=True, help="It indicates that a picking has been done. It will be 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"),
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 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 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'sale.order', context=c),
283 'picking_policy': 'direct',
284 'date_order': time.strftime('%Y-%m-%d'),
285 'order_policy': 'manual',
287 'user_id': lambda obj, cr, uid, context: uid,
288 'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'sale.order'),
289 'invoice_quantity': 'order',
290 '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'],
291 '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'],
292 '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'],
293 # '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,
298 def unlink(self, cr, uid, ids, context=None):
301 sale_orders = self.read(cr, uid, ids, ['state'], context=context)
303 for s in sale_orders:
304 if s['state'] in ['draft', 'cancel']:
305 unlink_ids.append(s['id'])
307 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Sale Order(s) which are already confirmed !'))
308 return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
310 def onchange_shop_id(self, cr, uid, ids, shop_id):
313 shop = self.pool.get('sale.shop').browse(cr, uid, shop_id)
314 v['project_id'] = shop.project_id.id
315 # Que faire si le client a une pricelist a lui ?
316 if shop.pricelist_id.id:
317 v['pricelist_id'] = shop.pricelist_id.id
318 #v['payment_default_id']=shop.payment_default_id.id
321 def action_cancel_draft(self, cr, uid, ids, *args):
324 cr.execute('select id from sale_order_line where order_id IN %s and state=%s',(tuple(ids),'cancel'))
325 line_ids = map(lambda x: x[0], cr.fetchall())
326 self.write(cr, uid, ids, {'state': 'draft', 'invoice_ids': [], 'shipped': 0})
327 self.pool.get('sale.order.line').write(cr, uid, line_ids, {'invoiced': False, 'state': 'draft', 'invoice_lines': [(6, 0, [])]})
328 wf_service = netsvc.LocalService("workflow")
330 # Deleting the existing instance of workflow for SO
331 wf_service.trg_delete(uid, 'sale.order', inv_id, cr)
332 wf_service.trg_create(uid, 'sale.order', inv_id, cr)
333 for (id,name) in self.name_get(cr, uid, ids):
334 message = _('Sale order ') + " '" + name + "' "+ _("is in draft state")
335 self.log(cr, uid, id, message)
338 def onchange_partner_id(self, cr, uid, ids, part):
340 return {'value': {'partner_invoice_id': False, 'partner_shipping_id': False, 'partner_order_id': False, 'payment_term': False, 'fiscal_position': False}}
342 addr = self.pool.get('res.partner').address_get(cr, uid, [part], ['delivery', 'invoice', 'contact'])
343 part = self.pool.get('res.partner').browse(cr, uid, part)
344 pricelist = part.property_product_pricelist and part.property_product_pricelist.id or False
345 payment_term = part.property_payment_term and part.property_payment_term.id or False
346 fiscal_position = part.property_account_position and part.property_account_position.id or False
347 dedicated_salesman = part.user_id and part.user_id.id or uid
350 'partner_invoice_id': addr['invoice'],
351 'partner_order_id': addr['contact'],
352 'partner_shipping_id': addr['delivery'],
353 'payment_term': payment_term,
354 'fiscal_position': fiscal_position,
355 'user_id': dedicated_salesman,
359 val['pricelist_id'] = pricelist
361 return {'value': val}
363 def shipping_policy_change(self, cr, uid, ids, policy, context=None):
369 if policy == 'prepaid':
371 elif policy == 'picking':
372 inv_qty = 'procurement'
373 return {'value': {'invoice_quantity': inv_qty}}
375 def write(self, cr, uid, ids, vals, context=None):
378 if 'order_policy' in vals:
379 if vals['order_policy'] == 'prepaid':
380 vals.update({'invoice_quantity': 'order'})
381 elif vals['order_policy'] == 'picking':
382 vals.update({'invoice_quantity': 'procurement'})
383 return super(sale_order, self).write(cr, uid, ids, vals, context=context)
385 def create(self, cr, uid, vals, context=None):
388 if 'order_policy' in vals:
389 if vals['order_policy'] == 'prepaid':
390 vals.update({'invoice_quantity': 'order'})
391 if vals['order_policy'] == 'picking':
392 vals.update({'invoice_quantity': 'procurement'})
393 return super(sale_order, self).create(cr, uid, vals, context=context)
395 def button_dummy(self, cr, uid, ids, context=None):
400 #FIXME: the method should return the list of invoices created (invoice_ids)
401 # and not the id of the last invoice created (res). The problem is that we
402 # cannot change it directly since the method is called by the sale order
403 # workflow and I suppose it expects a single id...
404 def _inv_get(self, cr, uid, order, context=None):
409 def _make_invoice(self, cr, uid, order, lines, context=None):
410 journal_obj = self.pool.get('account.journal')
411 inv_obj = self.pool.get('account.invoice')
416 journal_ids = journal_obj.search(cr, uid, [('type', '=','sale'),('company_id', '=', order.company_id.id)], limit=1)
418 raise osv.except_osv(_('Error !'),
419 _('There is no sale journal defined for this company: "%s" (id:%d)') % (order.company_id.name, order.company_id.id))
420 a = order.partner_id.property_account_receivable.id
421 if order.payment_term:
422 pay_term = order.payment_term.id
425 for preinv in order.invoice_ids:
426 if preinv.state not in ('cancel',):
427 for preline in preinv.invoice_line:
428 inv_line_id = self.pool.get('account.invoice.line').copy(cr, uid, preline.id, {'invoice_id': False, 'price_unit': -preline.price_unit})
429 lines.append(inv_line_id)
431 'name': order.client_order_ref or order.name,
432 'origin': order.name,
433 'type': 'out_invoice',
434 'reference': "P%dSO%d" % (order.partner_id.id, order.id),
436 'partner_id': order.partner_id.id,
437 'journal_id': journal_ids[0],
438 'address_invoice_id': order.partner_invoice_id.id,
439 'address_contact_id': order.partner_order_id.id,
440 'invoice_line': [(6, 0, lines)],
441 'currency_id': order.pricelist_id.currency_id.id,
442 'comment': order.note,
443 'payment_term': pay_term,
444 'fiscal_position': order.fiscal_position.id or order.partner_id.property_account_position.id,
445 'date_invoice' : context.get('date_invoice',False),
446 'company_id' : order.company_id.id,
447 'user_id':order.user_id and order.user_id.id or False
449 inv.update(self._inv_get(cr, uid, order))
450 inv_id = inv_obj.create(cr, uid, inv, context=context)
451 data = inv_obj.onchange_payment_term_date_invoice(cr, uid, [inv_id], pay_term, time.strftime('%Y-%m-%d'))
452 if data.get('value', False):
453 inv_obj.write(cr, uid, [inv_id], data['value'], context=context)
454 inv_obj.button_compute(cr, uid, [inv_id])
457 def action_invoice_create(self, cr, uid, ids, grouped=False, states=['confirmed', 'done', 'exception'], date_inv = False, context=None):
461 picking_obj = self.pool.get('stock.picking')
462 invoice = self.pool.get('account.invoice')
465 # If date was specified, use it as date invoiced, usefull when invoices are generated this month and put the
466 # last day of the last month as invoice date
468 context['date_inv'] = date_inv
469 for o in self.browse(cr, uid, ids):
471 for line in o.order_line:
473 raise osv.except_osv(_('Error !'), _('The Sale Order already has some lines invoiced. You should continue the billing process by line.'))
474 elif (line.state in states):
475 lines.append(line.id)
476 created_lines = self.pool.get('sale.order.line').invoice_line_create(cr, uid, lines)
478 invoices.setdefault(o.partner_id.id, []).append((o, created_lines))
480 for o in self.browse(cr, uid, ids):
481 for i in o.invoice_ids:
482 if i.state == 'draft':
484 for val in invoices.values():
486 res = self._make_invoice(cr, uid, val[0][0], reduce(lambda x,y: x + y, [l for o,l in val], []), context=context)
489 invoice_ref += o.name + '|'
490 self.write(cr, uid, [o.id], {'state': 'progress'})
491 if o.order_policy == 'picking':
492 picking_obj.write(cr, uid, map(lambda x: x.id, o.picking_ids), {'invoice_state': 'invoiced'})
493 cr.execute('insert into sale_order_invoice_rel (order_id,invoice_id) values (%s,%s)', (o.id, res))
494 invoice.write(cr, uid, [res], {'origin': invoice_ref, 'name': invoice_ref})
496 for order, il in val:
497 res = self._make_invoice(cr, uid, order, il, context=context)
498 invoice_ids.append(res)
499 self.write(cr, uid, [order.id], {'state': 'progress'})
500 if order.order_policy == 'picking':
501 picking_obj.write(cr, uid, map(lambda x: x.id, order.picking_ids), {'invoice_state': 'invoiced'})
502 cr.execute('insert into sale_order_invoice_rel (order_id,invoice_id) values (%s,%s)', (order.id, res))
505 def action_invoice_cancel(self, cr, uid, ids, context=None):
508 for sale in self.browse(cr, uid, ids, context=context):
509 for line in sale.order_line:
511 for iline in line.invoice_lines:
512 if iline.invoice_id and iline.invoice_id.state == 'cancel':
516 self.pool.get('sale.order.line').write(cr, uid, [line.id], {'invoiced': invoiced})
517 self.write(cr, uid, ids, {'state': 'invoice_except', 'invoice_ids': False})
520 def action_invoice_end(self, cr, uid, ids, context=None):
524 for order in self.browse(cr, uid, ids, context=context):
525 for line in order.order_line:
526 if line.state == 'exception':
527 self.pool.get('sale.order.line').write(cr, uid, [line.id], {'state': 'confirmed'}, context=context)
529 if order.state == 'invoice_except':
530 self.write(cr, uid, [order.id], {'state' : 'progress'}, context=context)
534 def action_cancel(self, cr, uid, ids, context=None):
538 sale_order_line_obj = self.pool.get('sale.order.line')
539 for sale in self.browse(cr, uid, ids, context=context):
540 for pick in sale.picking_ids:
541 if pick.state not in ('draft', 'cancel'):
542 raise osv.except_osv(
543 _('Could not cancel sale order !'),
544 _('You must first cancel all picking attached to this sale order.'))
545 for r in self.read(cr, uid, ids, ['picking_ids']):
546 for pick in r['picking_ids']:
547 wf_service = netsvc.LocalService("workflow")
548 wf_service.trg_validate(uid, 'stock.picking', pick, 'button_cancel', cr)
549 for inv in sale.invoice_ids:
550 if inv.state not in ('draft', 'cancel'):
551 raise osv.except_osv(
552 _('Could not cancel this sale order !'),
553 _('You must first cancel all invoices attached to this sale order.'))
554 for r in self.read(cr, uid, ids, ['invoice_ids']):
555 for inv in r['invoice_ids']:
556 wf_service = netsvc.LocalService("workflow")
557 wf_service.trg_validate(uid, 'account.invoice', inv, 'invoice_cancel', cr)
558 sale_order_line_obj.write(cr, uid, [l.id for l in sale.order_line],
560 message = _('Sale order') + " '" + sale.name + _(" is cancelled")
561 self.log(cr, uid, sale.id, message)
562 self.write(cr, uid, ids, {'state': 'cancel'})
565 def action_wait(self, cr, uid, ids, *args):
566 product_obj = self.pool.get('product.product')
567 for o in self.browse(cr, uid, ids):
568 if (o.order_policy == 'manual'):
569 self.write(cr, uid, [o.id], {'state': 'manual', 'date_confirm': time.strftime('%Y-%m-%d')})
571 self.write(cr, uid, [o.id], {'state': 'progress', 'date_confirm': time.strftime('%Y-%m-%d')})
572 self.pool.get('sale.order.line').button_confirm(cr, uid, [x.id for x in o.order_line])
573 message = _('Quotation') + " '" + o.name + "' "+ _("is converted to Sale order")
574 self.log(cr, uid, o.id, message)
577 def procurement_lines_get(self, cr, uid, ids, *args):
579 for order in self.browse(cr, uid, ids, context={}):
580 for line in order.order_line:
581 if line.procurement_id:
582 res.append(line.procurement_id.id)
585 # if mode == 'finished':
586 # returns True if all lines are done, False otherwise
587 # if mode == 'canceled':
588 # returns True if there is at least one canceled line, False otherwise
589 def test_state(self, cr, uid, ids, mode, *args):
590 assert mode in ('finished', 'canceled'), _("invalid mode for test_state")
595 write_cancel_ids = []
596 for order in self.browse(cr, uid, ids, context={}):
597 for line in order.order_line:
598 if (not line.procurement_id) or (line.procurement_id.state=='done'):
599 if line.state != 'done':
600 write_done_ids.append(line.id)
603 if line.procurement_id:
604 if (line.procurement_id.state == 'cancel'):
606 if line.state != 'exception':
607 write_cancel_ids.append(line.id)
611 self.pool.get('sale.order.line').write(cr, uid, write_done_ids, {'state': 'done'})
613 self.pool.get('sale.order.line').write(cr, uid, write_cancel_ids, {'state': 'exception'})
615 if mode == 'finished':
617 elif mode == 'canceled':
623 def action_ship_create(self, cr, uid, ids, *args):
625 company = self.pool.get('res.users').browse(cr, uid, uid).company_id
626 for order in self.browse(cr, uid, ids, context={}):
628 output_id = order.shop_id.warehouse_id.lot_output_id.id
630 for line in order.order_line:
632 date_planned = datetime.now() + relativedelta(days=line.delay or 0.0)
633 date_planned = (date_planned - timedelta(days=company.security_lead)).strftime('%Y-%m-%d %H:%M:%S')
635 if line.state == 'done':
638 if line.product_id and line.product_id.product_tmpl_id.type in ('product', 'consu'):
639 location_id = order.shop_id.warehouse_id.lot_stock_id.id
641 loc_dest_id = order.partner_id.property_stock_customer.id
642 pick_name = self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.out')
643 picking_id = self.pool.get('stock.picking').create(cr, uid, {
645 'origin': order.name,
648 'move_type': order.picking_policy,
650 'address_id': order.partner_shipping_id.id,
652 'invoice_state': (order.order_policy=='picking' and '2binvoiced') or 'none',
653 'company_id': order.company_id.id,
655 move_id = self.pool.get('stock.move').create(cr, uid, {
656 'name': line.name[:64],
657 'picking_id': picking_id,
658 'product_id': line.product_id.id,
659 'date_planned': date_planned,
660 'date_expected': date_planned,
661 'product_qty': line.product_uom_qty,
662 'product_uom': line.product_uom.id,
663 'product_uos_qty': line.product_uos_qty,
664 'product_uos': (line.product_uos and line.product_uos.id)\
665 or line.product_uom.id,
666 'product_packaging': line.product_packaging.id,
667 'address_id': line.address_allotment_id.id or order.partner_shipping_id.id,
668 'location_id': location_id,
669 'location_dest_id': output_id,
670 'sale_line_id': line.id,
671 'tracking_id': False,
675 'company_id': order.company_id.id,
678 proc_id = self.pool.get('procurement.order').create(cr, uid, {
680 'origin': order.name,
681 'date_planned': date_planned,
682 'product_id': line.product_id.id,
683 'product_qty': line.product_uom_qty,
684 'product_uom': line.product_uom.id,
685 'product_uos_qty': (line.product_uos and line.product_uos_qty)\
686 or line.product_uom_qty,
687 'product_uos': (line.product_uos and line.product_uos.id)\
688 or line.product_uom.id,
689 'location_id': order.shop_id.warehouse_id.lot_stock_id.id,
690 'procure_method': line.type,
692 'property_ids': [(6, 0, [x.id for x in line.property_ids])],
693 'company_id': order.company_id.id,
695 proc_ids.append(proc_id)
696 self.pool.get('sale.order.line').write(cr, uid, [line.id], {'procurement_id': proc_id})
700 wf_service = netsvc.LocalService("workflow")
701 wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
703 for proc_id in proc_ids:
704 wf_service = netsvc.LocalService("workflow")
705 wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_confirm', cr)
707 if order.state == 'shipping_except':
708 val['state'] = 'progress'
710 if (order.order_policy == 'manual'):
711 for line in order.order_line:
712 if (not line.invoiced) and (line.state not in ('cancel', 'draft')):
713 val['state'] = 'manual'
715 self.write(cr, uid, [order.id], val)
718 def action_ship_end(self, cr, uid, ids, context=None):
721 for order in self.browse(cr, uid, ids, context=context):
722 val = {'shipped': True}
723 if order.state == 'shipping_except':
724 val['state'] = 'progress'
725 if (order.order_policy == 'manual'):
726 for line in order.order_line:
727 if (not line.invoiced) and (line.state not in ('cancel', 'draft')):
728 val['state'] = 'manual'
730 for line in order.order_line:
732 if line.state == 'exception':
733 towrite.append(line.id)
735 self.pool.get('sale.order.line').write(cr, uid, towrite, {'state': 'done'}, context=context)
736 self.write(cr, uid, [order.id], val)
739 def _log_event(self, cr, uid, ids, factor=0.7, name='Open Order'):
740 invs = self.read(cr, uid, ids, ['date_order', 'partner_id', 'amount_untaxed'])
742 part = inv['partner_id'] and inv['partner_id'][0]
743 pr = inv['amount_untaxed'] or 0.0
744 partnertype = 'customer'
747 'name': 'Order: '+name,
749 'description': 'Order '+str(inv['id']),
752 'date': time.strftime('%Y-%m-%d'),
755 'partner_type': partnertype,
757 'planned_revenue': pr,
761 self.pool.get('res.partner.event').create(cr, uid, event)
763 def has_stockable_products(self, cr, uid, ids, *args):
764 for order in self.browse(cr, uid, ids):
765 for order_line in order.order_line:
766 if order_line.product_id and order_line.product_id.product_tmpl_id.type in ('product', 'consu'):
771 # TODO add a field price_unit_uos
772 # - update it on change product and unit price
773 # - use it in report if there is a uos
774 class sale_order_line(osv.osv):
775 def _amount_line(self, cr, uid, ids, field_name, arg, context=None):
776 tax_obj = self.pool.get('account.tax')
777 cur_obj = self.pool.get('res.currency')
779 context = context or {}
780 for line in self.browse(cr, uid, ids, context=context):
781 price = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
782 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)
783 cur = line.order_id.pricelist_id.currency_id
784 res[line.id] = cur_obj.round(cr, uid, cur, taxes['total'])
787 def _number_packages(self, cr, uid, ids, field_name, arg, context=None):
791 for line in self.browse(cr, uid, ids, context=context):
793 res[line.id] = int((line.product_uom_qty+line.product_packaging.qty-0.0001) / line.product_packaging.qty)
798 _name = 'sale.order.line'
799 _description = 'Sale Order Line'
801 'order_id': fields.many2one('sale.order', 'Order Reference', required=True, ondelete='cascade', select=True, readonly=True, states={'draft':[('readonly',False)]}),
802 'name': fields.char('Description', size=256, required=True, select=True, readonly=True, states={'draft':[('readonly',False)]}),
803 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of sale order lines."),
804 '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)]}),
805 'product_id': fields.many2one('product.product', 'Product', domain=[('sale_ok', '=', True)], change_default=True),
806 'invoice_lines': fields.many2many('account.invoice.line', 'sale_order_line_invoice_rel', 'order_line_id', 'invoice_id', 'Invoice Lines', readonly=True),
807 'invoiced': fields.boolean('Invoiced', readonly=True),
808 'procurement_id': fields.many2one('procurement.order', 'Procurement'),
809 'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Sale Price'), readonly=True, states={'draft':[('readonly',False)]}),
810 'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal', digits_compute= dp.get_precision('Sale Price')),
811 'tax_id': fields.many2many('account.tax', 'sale_order_tax', 'order_line_id', 'tax_id', 'Taxes', readonly=True, states={'draft':[('readonly',False)]}),
812 'type': fields.selection([('make_to_stock', 'from stock'), ('make_to_order', 'on order')], 'Procurement Method', required=True, readonly=True, states={'draft':[('readonly',False)]}),
813 'property_ids': fields.many2many('mrp.property', 'sale_order_line_property_rel', 'order_id', 'property_id', 'Properties', readonly=True, states={'draft':[('readonly',False)]}),
814 'address_allotment_id': fields.many2one('res.partner.address', 'Allotment Partner'),
815 'product_uom_qty': fields.float('Quantity (UoM)', digits=(16, 2), required=True, readonly=True, states={'draft':[('readonly',False)]}),
816 'product_uom': fields.many2one('product.uom', 'Unit of Measure ', required=True, readonly=True, states={'draft':[('readonly',False)]}),
817 'product_uos_qty': fields.float('Quantity (UoS)', readonly=True, states={'draft':[('readonly',False)]}),
818 'product_uos': fields.many2one('product.uom', 'Product UoS'),
819 'product_packaging': fields.many2one('product.packaging', 'Packaging'),
820 'move_ids': fields.one2many('stock.move', 'sale_line_id', 'Inventory Moves', readonly=True),
821 'discount': fields.float('Discount (%)', digits=(16, 2), readonly=True, states={'draft':[('readonly',False)]}),
822 'number_packages': fields.function(_number_packages, method=True, type='integer', string='Number Packages'),
823 'notes': fields.text('Notes'),
824 'th_weight': fields.float('Weight', readonly=True, states={'draft':[('readonly',False)]}),
825 'state': fields.selection([('draft', 'Draft'),('confirmed', 'Confirmed'),('done', 'Done'),('cancel', 'Cancelled'),('exception', 'Exception')], 'State', required=True, readonly=True,
826 help='* The \'Draft\' state is set when the related sale order in draft state. \
827 \n* The \'Confirmed\' state is set when the related sale order is confirmed. \
828 \n* The \'Exception\' state is set when the related sale order is set as exception. \
829 \n* The \'Done\' state is set when the sale order line has been picked. \
830 \n* The \'Cancelled\' state is set when a user cancel the sale order related.'),
831 'order_partner_id': fields.related('order_id', 'partner_id', type='many2one', relation='res.partner', string='Customer'),
832 'salesman_id':fields.related('order_id', 'user_id', type='many2one', relation='res.users', string='Salesman'),
833 'company_id': fields.related('order_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True, states={'draft':[('readonly',False)]}),
835 _order = 'sequence, id'
839 'product_uom_qty': 1,
840 'product_uos_qty': 1,
844 'type': 'make_to_stock',
845 'product_packaging': False
848 def create_sale_order_line_invoice(self, cr, uid, ids, context=None):
851 context.update({'active_ids' : ids,'active_id' : ids})
852 self.pool.get('sale.order.line.make.invoice').make_invoices(cr, uid, ids, context=context)
855 def invoice_line_create(self, cr, uid, ids, context=None):
859 def _get_line_qty(line):
860 if (line.order_id.invoice_quantity=='order') or not line.procurement_id:
862 return line.product_uos_qty or 0.0
863 return line.product_uom_qty
865 return self.pool.get('procurement.order').quantity_get(cr, uid,
866 line.procurement_id.id, context=context)
868 def _get_line_uom(line):
869 if (line.order_id.invoice_quantity=='order') or not line.procurement_id:
871 return line.product_uos.id
872 return line.product_uom.id
874 return self.pool.get('procurement.order').uom_get(cr, uid,
875 line.procurement_id.id, context=context)
879 for line in self.browse(cr, uid, ids, context=context):
880 if not line.invoiced:
882 a = line.product_id.product_tmpl_id.property_account_income.id
884 a = line.product_id.categ_id.property_account_income_categ.id
886 raise osv.except_osv(_('Error !'),
887 _('There is no income account defined ' \
888 'for this product: "%s" (id:%d)') % \
889 (line.product_id.name, line.product_id.id,))
891 a = self.pool.get('ir.property').get(cr, uid,
892 'property_account_income_categ', 'product.category',
894 uosqty = _get_line_qty(line)
895 uos_id = _get_line_uom(line)
898 pu = round(line.price_unit * line.product_uom_qty / uosqty,
899 self.pool.get('decimal.precision').precision_get(cr, uid, 'Sale Price'))
900 fpos = line.order_id.fiscal_position or False
901 a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
903 raise osv.except_osv(_('Error !'),
904 _('There is no income category account defined in default Properties for Product Category or Fiscal Position is not defined !'))
905 inv_id = self.pool.get('account.invoice.line').create(cr, uid, {
907 'origin': line.order_id.name,
911 'discount': line.discount,
913 'product_id': line.product_id.id or False,
914 'invoice_line_tax_id': [(6, 0, [x.id for x in line.tax_id])],
916 'account_analytic_id': line.order_id.project_id and line.order_id.project_id.id or False,
918 cr.execute('insert into sale_order_line_invoice_rel (order_line_id,invoice_id) values (%s,%s)', (line.id, inv_id))
919 self.write(cr, uid, [line.id], {'invoiced': True})
920 sales[line.order_id.id] = True
921 create_ids.append(inv_id)
922 # Trigger workflow events
923 wf_service = netsvc.LocalService("workflow")
924 for sid in sales.keys():
925 wf_service.trg_write(uid, 'sale.order', sid, cr)
928 def button_cancel(self, cr, uid, ids, context=None):
931 for line in self.browse(cr, uid, ids, context=context):
933 raise osv.except_osv(_('Invalid action !'), _('You cannot cancel a sale order line that has already been invoiced !'))
934 for move_line in line.move_ids:
935 if move_line.state != 'cancel':
936 raise osv.except_osv(
937 _('Could not cancel sale order line!'),
938 _('You must first cancel stock moves attached to this sale order line.'))
939 message = _('Sale order line') + " '" + line.name + "' "+_("is cancelled")
940 self.log(cr, uid, id, message)
941 return self.write(cr, uid, ids, {'state': 'cancel'})
943 def button_confirm(self, cr, uid, ids, context=None):
946 return self.write(cr, uid, ids, {'state': 'confirmed'})
948 def button_done(self, cr, uid, ids, context=None):
951 wf_service = netsvc.LocalService("workflow")
952 res = self.write(cr, uid, ids, {'state': 'done'})
953 for line in self.browse(cr, uid, ids, context=context):
954 wf_service.trg_write(uid, 'sale.order', line.order_id.id, cr)
957 def uos_change(self, cr, uid, ids, product_uos, product_uos_qty=0, product_id=None):
958 product_obj = self.pool.get('product.product')
960 return {'value': {'product_uom': product_uos,
961 'product_uom_qty': product_uos_qty}, 'domain': {}}
963 product = product_obj.browse(cr, uid, product_id)
965 'product_uom': product.uom_id.id,
967 # FIXME must depend on uos/uom of the product and not only of the coeff.
970 'product_uom_qty': product_uos_qty / product.uos_coeff,
971 'th_weight': product_uos_qty / product.uos_coeff * product.weight
973 except ZeroDivisionError:
975 return {'value': value}
977 def copy_data(self, cr, uid, id, default=None, context=None):
982 default.update({'state': 'draft', 'move_ids': [], 'invoiced': False, 'invoice_lines': []})
983 return super(sale_order_line, self).copy_data(cr, uid, id, default, context=context)
985 def product_id_change(self, cr, uid, ids, pricelist, product, qty=0,
986 uom=False, qty_uos=0, uos=False, name='', partner_id=False,
987 lang=False, update_tax=True, date_order=False, packaging=False, fiscal_position=False, flag=False):
989 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.'))
991 product_uom_obj = self.pool.get('product.uom')
992 partner_obj = self.pool.get('res.partner')
993 product_obj = self.pool.get('product.product')
995 lang = partner_obj.browse(cr, uid, partner_id).lang
996 context = {'lang': lang, 'partner_id': partner_id}
999 return {'value': {'th_weight': 0, 'product_packaging': False,
1000 'product_uos_qty': qty}, 'domain': {'product_uom': [],
1004 date_order = time.strftime('%Y-%m-%d')
1007 product_obj = product_obj.browse(cr, uid, product, context=context)
1008 if not packaging and product_obj.packaging:
1009 packaging = product_obj.packaging[0].id
1010 result['product_packaging'] = packaging
1013 default_uom = product_obj.uom_id and product_obj.uom_id.id
1014 pack = self.pool.get('product.packaging').browse(cr, uid, packaging, context=context)
1015 q = product_uom_obj._compute_qty(cr, uid, uom, pack.qty, default_uom)
1016 # qty = qty - qty % q + q
1017 if qty and (q and not (qty % q) == 0):
1021 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)
1022 warn_msg = warn_msg + "\n\n" + _("EAN: ") + str(ean) + _(" Quantity: ") + str(qty_pack) + _(" Type of ul: ") + str(type_ul.name)
1024 'title': _('Picking Information !'),
1027 result['product_uom_qty'] = qty
1031 uom2 = product_uom_obj.browse(cr, uid, uom)
1032 if product_obj.uom_id.category_id.id != uom2.category_id.id:
1036 if product_obj.uos_id:
1037 uos2 = product_uom_obj.browse(cr, uid, uos)
1038 if product_obj.uos_id.category_id.id != uos2.category_id.id:
1042 if product_obj.description_sale:
1043 result['notes'] = product_obj.description_sale
1044 fpos = fiscal_position and self.pool.get('account.fiscal.position').browse(cr, uid, fiscal_position) or False
1045 if update_tax: #The quantity only have changed
1046 result['delay'] = (product_obj.sale_delay or 0.0)
1047 partner = partner_obj.browse(cr, uid, partner_id)
1048 result['tax_id'] = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, product_obj.taxes_id)
1049 result.update({'type': product_obj.procure_method})
1052 result['name'] = self.pool.get('product.product').name_get(cr, uid, [product_obj.id], context=context)[0][1]
1054 if (not uom) and (not uos):
1055 result['product_uom'] = product_obj.uom_id.id
1056 if product_obj.uos_id:
1057 result['product_uos'] = product_obj.uos_id.id
1058 result['product_uos_qty'] = qty * product_obj.uos_coeff
1059 uos_category_id = product_obj.uos_id.category_id.id
1061 result['product_uos'] = False
1062 result['product_uos_qty'] = qty
1063 uos_category_id = False
1064 result['th_weight'] = qty * product_obj.weight
1065 domain = {'product_uom':
1066 [('category_id', '=', product_obj.uom_id.category_id.id)],
1068 [('category_id', '=', uos_category_id)]}
1070 elif uos and not uom: # only happens if uom is False
1071 result['product_uom'] = product_obj.uom_id and product_obj.uom_id.id
1072 result['product_uom_qty'] = qty_uos / product_obj.uos_coeff
1073 result['th_weight'] = result['product_uom_qty'] * product_obj.weight
1074 elif uom: # whether uos is set or not
1075 default_uom = product_obj.uom_id and product_obj.uom_id.id
1076 q = product_uom_obj._compute_qty(cr, uid, uom, qty, default_uom)
1077 if product_obj.uos_id:
1078 result['product_uos'] = product_obj.uos_id.id
1079 result['product_uos_qty'] = qty * product_obj.uos_coeff
1081 result['product_uos'] = False
1082 result['product_uos_qty'] = qty
1083 result['th_weight'] = q * product_obj.weight # Round the quantity up
1086 uom2 = product_obj.uom_id
1087 if (product_obj.type=='product') and (product_obj.virtual_available * uom2.factor < qty * product_obj.uom_id.factor) \
1088 and (product_obj.procure_method=='make_to_stock'):
1090 'title': _('Not enough stock !'),
1091 'message': _('You plan to sell %.2f %s but you only have %.2f %s available !\nThe real stock is %.2f %s. (without reservations)') %
1092 (qty, uom2 and uom2.name or product_obj.uom_id.name,
1093 max(0,product_obj.virtual_available), product_obj.uom_id.name,
1094 max(0,product_obj.qty_available), product_obj.uom_id.name)
1102 'title': 'No Pricelist !',
1104 'You have to select a pricelist or a customer in the sale form !\n'
1105 'Please set one before choosing a product.'
1108 price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist],
1109 product, qty or 1.0, partner_id, {
1115 'title': 'No valid pricelist line found !',
1117 "Couldn't find a pricelist line matching this product and quantity.\n"
1118 "You have to change either the product, the quantity or the pricelist."
1121 result.update({'price_unit': price})
1122 return {'value': result, 'domain': domain, 'warning': warning}
1124 def product_uom_change(self, cursor, user, ids, pricelist, product, qty=0,
1125 uom=False, qty_uos=0, uos=False, name='', partner_id=False,
1126 lang=False, update_tax=True, date_order=False):
1127 res = self.product_id_change(cursor, user, ids, pricelist, product,
1128 qty=qty, uom=uom, qty_uos=qty_uos, uos=uos, name=name,
1129 partner_id=partner_id, lang=lang, update_tax=update_tax,
1130 date_order=date_order)
1131 if 'product_uom' in res['value']:
1132 del res['value']['product_uom']
1134 res['value']['price_unit'] = 0.0
1137 def unlink(self, cr, uid, ids, context=None):
1140 """Allows to delete sale order lines in draft,cancel states"""
1141 for rec in self.browse(cr, uid, ids, context=context):
1142 if rec.state not in ['draft', 'cancel']:
1143 raise osv.except_osv(_('Invalid action !'), _('Cannot delete a sale order line which is %s !')%(rec.state,))
1144 return super(sale_order_line, self).unlink(cr, uid, ids, context=context)
1149 class sale_config_picking_policy(osv.osv_memory):
1150 _name = 'sale.config.picking_policy'
1151 _inherit = 'res.config'
1154 'name': fields.char('Name', size=64),
1155 'picking_policy': fields.selection([
1156 ('direct', 'Direct Delivery'),
1157 ('one', 'All at Once')
1158 ], 'Picking Default Policy', required=True),
1159 'order_policy': fields.selection([
1160 ('manual', 'Invoice Based on Sales Orders'),
1161 ('picking', 'Invoice Based on Deliveries'),
1162 ], 'Shipping Default Policy', required=True,
1163 help="You can generate invoices based on sales orders or based on shippings."),
1164 'step': fields.selection([
1165 ('one', 'Delivery Order Only'),
1166 ('two', 'Picking List & Delivery Order')
1167 ], 'Steps To Deliver a Sale Order', required=True,
1168 help="By default, OpenERP is able to manage complex routing and paths "\
1169 "of products in your warehouse and partner locations. This will configure "\
1170 "the most common and simple methods to deliver products to the customer "\
1171 "in one or two operations by the worker.")
1174 'picking_policy': lambda *a: 'direct',
1175 'order_policy': lambda *a: 'manual',
1176 'step': lambda *a: 'one'
1179 def execute(self, cr, uid, ids, context=None):
1182 for o in self.browse(cr, uid, ids, context=context):
1183 ir_values_obj = self.pool.get('ir.values')
1184 ir_values_obj.set(cr, uid, 'default', False, 'picking_policy', ['sale.order'], o.picking_policy)
1185 ir_values_obj.set(cr, uid, 'default', False, 'order_policy', ['sale.order'], o.order_policy)
1187 md = self.pool.get('ir.model.data')
1188 group_id = md._get_id(cr, uid, 'base', 'group_no_one')
1189 group_id = md.browse(cr, uid, group_id, context=context).res_id
1190 menu_id = md._get_id(cr, uid, 'stock', 'menu_action_picking_tree_delivery')
1191 menu_id = md.browse(cr, uid, menu_id, context=context).res_id
1192 self.pool.get('ir.ui.menu').write(cr, uid, [menu_id], {'groups_id': [(6, 0, [group_id])]})
1193 location_id = md._get_id(cr, uid, 'stock', 'stock_location_output')
1194 location_id = md.browse(cr, uid, location_id, context=context).res_id
1195 self.pool.get('stock.location').write(cr, uid, [location_id], {'chained_auto_packing': 'transparent'})
1197 sale_config_picking_policy()
1199 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: