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 ##############################################################################
24 from osv import fields, osv
25 from datetime import datetime
26 from dateutil.relativedelta import relativedelta
27 from tools import config
28 from tools.translate import _
30 import decimal_precision as dp
33 class sale_shop(osv.osv):
35 _description = "Sale Shop"
37 'name': fields.char('Shop Name', size=64, required=True),
38 'payment_default_id': fields.many2one('account.payment.term', 'Default Payment Term', required=True),
39 'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse'),
40 'pricelist_id': fields.many2one('product.pricelist', 'Pricelist'),
41 'project_id': fields.many2one('account.analytic.account', 'Analytic Account'),
42 'company_id': fields.many2one('res.company', 'Company'),
47 def _incoterm_get(self, cr, uid, context=None):
50 cr.execute('select code, code||\', \'||name from stock_incoterms where active')
53 class sale_order(osv.osv):
55 _description = "Sale Order"
57 def copy(self, cr, uid, id, default=None, context=None):
67 'name': self.pool.get('ir.sequence').get(cr, uid, 'sale.order'),
69 return super(sale_order, self).copy(cr, uid, id, default, context=context)
71 def _amount_line_tax(self, cr, uid, line, context=None):
75 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']:
79 def _amount_all(self, cr, uid, ids, field_name, arg, context=None):
83 cur_obj = self.pool.get('res.currency')
84 for order in self.browse(cr, uid, ids, context):
86 'amount_untaxed': 0.0,
91 cur = order.pricelist_id.currency_id
92 for line in order.order_line:
93 val1 += line.price_subtotal
94 val += self._amount_line_tax(cr, uid, line, context=context)
95 res[order.id]['amount_tax'] = cur_obj.round(cr, uid, cur, val)
96 res[order.id]['amount_untaxed'] = cur_obj.round(cr, uid, cur, val1)
97 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 = ANY(%s) GROUP BY mp.state, p.sale_id''',(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
150 res[sale.id] = min(100.0, tot * 100.0 / (sale.amount_untaxed or 1.00))
155 def _invoiced(self, cursor, user, ids, name, arg, context=None):
159 for sale in self.browse(cursor, user, ids, context=context):
161 for invoice in sale.invoice_ids:
162 if invoice.state != 'paid':
165 if not sale.invoice_ids:
169 def _invoiced_search(self, cursor, user, obj, name, args, context=None):
179 clause += 'AND inv.state = \'paid\''
181 clause += 'AND inv.state <> \'paid\''
183 cursor.execute('SELECT rel.order_id ' \
184 'FROM sale_order_invoice_rel AS rel, account_invoice AS inv ' \
185 'WHERE rel.invoice_id = inv.id ' + clause)
186 res = cursor.fetchall()
188 cursor.execute('SELECT sale.id ' \
189 'FROM sale_order AS sale ' \
190 'WHERE sale.id NOT IN ' \
191 '(SELECT rel.order_id ' \
192 'FROM sale_order_invoice_rel AS rel)')
193 res.extend(cursor.fetchall())
195 return [('id', '=', 0)]
196 return [('id', 'in', [x[0] for x in res])]
198 def _get_order(self, cr, uid, ids, context=None):
202 for line in self.pool.get('sale.order.line').browse(cr, uid, ids, context=context):
203 result[line.order_id.id] = True
207 'name': fields.char('Order Reference', size=64, required=True, 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),
212 'state': fields.selection([
213 ('draft', 'Quotation'),
214 ('waiting_date', 'Waiting Schedule'),
215 ('manual', 'Manual In Progress'),
216 ('progress', 'In Progress'),
217 ('shipping_except', 'Shipping Exception'),
218 ('invoice_except', 'Invoice Exception'),
220 ('cancel', 'Cancelled')
221 ], '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),
222 'date_order': fields.date('Ordered Date', required=True, readonly=True, states={'draft': [('readonly', False)]}),
223 'create_date': fields.date('Creation Date', readonly=True),
224 'date_confirm': fields.date('Confirmation Date', readonly=True),
225 'user_id': fields.many2one('res.users', 'Salesman', states={'draft': [('readonly', False)]}, select=True),
226 'partner_id': fields.many2one('res.partner', 'Customer', readonly=True, states={'draft': [('readonly', False)]}, required=True, change_default=True, select=True),
227 'partner_invoice_id': fields.many2one('res.partner.address', 'Invoice Address', readonly=True, required=True, states={'draft': [('readonly', False)]}),
228 '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."),
229 '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"),
231 'incoterm': fields.selection(_incoterm_get, 'Incoterm', size=3),
232 'picking_policy': fields.selection([('direct', 'Partial Delivery'), ('one', 'Complete Delivery')],
233 'Picking Policy', required=True, help="""If you don't have enough stock available to deliver all at once, do you accept partial shipments or not?"""),
234 'order_policy': fields.selection([
235 ('prepaid', 'Payment Before Delivery'),
236 ('manual', 'Shipping & Manual Invoice'),
237 ('postpaid', 'Invoice on Order After Delivery'),
238 ('picking', 'Invoice from Picking'),
239 ], 'Shipping Policy', required=True, readonly=True, states={'draft': [('readonly', False)]},
240 help="""The Shipping Policy is used to synchronise invoice and delivery operations.
241 - The 'Pay before delivery' choice will first generate the invoice and then generate the picking order after the payment of this invoice.
242 - 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.
243 - The 'Invoice on Order After Delivery' choice will generate the draft invoice based on sale order after all picking lists have been finished.
244 - The 'Invoice from the picking' choice is used to create an invoice during the picking process."""),
245 'pricelist_id': fields.many2one('product.pricelist', 'Pricelist', required=True, readonly=True, states={'draft': [('readonly', False)]}, help="Pricelist version for current sale order"),
246 'project_id': fields.many2one('account.analytic.account', 'Analytic Account', readonly=True, states={'draft': [('readonly', False)]}),
248 'order_line': fields.one2many('sale.order.line', 'order_id', 'Order Lines', readonly=True, states={'draft': [('readonly', False)]}),
249 '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)."),
250 '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"),
251 'shipped': fields.boolean('Picked', readonly=True),
252 'picked_rate': fields.function(_picked_rate, method=True, string='Picked', type='float'),
253 'invoiced_rate': fields.function(_invoiced_rate, method=True, string='Invoiced', type='float'),
254 'invoiced': fields.function(_invoiced, method=True, string='Paid',
255 fnct_search=_invoiced_search, type='boolean'),
256 'note': fields.text('Notes', translate=True),
258 'amount_untaxed': fields.function(_amount_all, method=True, digits_compute= dp.get_precision('Sale Price'), string='Untaxed Amount',
260 'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line'], 10),
261 'sale.order.line': (_get_order, ['price_unit', 'tax_id', 'discount', 'product_uom_qty'], 10),
264 'amount_tax': fields.function(_amount_all, method=True, digits_compute= dp.get_precision('Sale Price'), string='Taxes',
266 'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line'], 10),
267 'sale.order.line': (_get_order, ['price_unit', 'tax_id', 'discount', 'product_uom_qty'], 10),
270 'amount_total': fields.function(_amount_all, method=True, digits_compute= dp.get_precision('Sale Price'), string='Total',
272 'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line'], 10),
273 'sale.order.line': (_get_order, ['price_unit', 'tax_id', 'discount', 'product_uom_qty'], 10),
277 '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),
278 'payment_term': fields.many2one('account.payment.term', 'Payment Term'),
279 'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position'),
280 'company_id': fields.related('shop_id','company_id',type='many2one',relation='res.company',string='Company',store=True)
283 'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'sale.order', context=c),
284 'picking_policy': lambda *a: 'direct',
285 'date_order': lambda *a: time.strftime('%Y-%m-%d'),
286 'order_policy': lambda *a: 'manual',
287 'state': lambda *a: 'draft',
288 'user_id': lambda obj, cr, uid, context: uid,
289 'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'sale.order'),
290 'invoice_quantity': lambda *a: 'order',
291 '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'],
292 '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'],
293 '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'],
294 # '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,
299 def unlink(self, cr, uid, ids, context=None):
302 sale_orders = self.read(cr, uid, ids, ['state'], context=context)
304 for s in sale_orders:
305 if s['state'] in ['draft', 'cancel']:
306 unlink_ids.append(s['id'])
308 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Sale Order(s) which are already confirmed !'))
309 return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
311 def onchange_shop_id(self, cr, uid, ids, shop_id):
314 shop = self.pool.get('sale.shop').browse(cr, uid, shop_id)
315 v['project_id'] = shop.project_id.id
316 # Que faire si le client a une pricelist a lui ?
317 if shop.pricelist_id.id:
318 v['pricelist_id'] = shop.pricelist_id.id
319 #v['payment_default_id']=shop.payment_default_id.id
322 def action_cancel_draft(self, cr, uid, ids, *args):
325 cr.execute('select id from sale_order_line where order_id = ANY(%s) and state=%s',(ids,'cancel'))
326 line_ids = map(lambda x: x[0], cr.fetchall())
327 self.write(cr, uid, ids, {'state': 'draft', 'invoice_ids': [], 'shipped': 0})
328 self.pool.get('sale.order.line').write(cr, uid, line_ids, {'invoiced': False, 'state': 'draft', 'invoice_lines': [(6, 0, [])]})
329 wf_service = netsvc.LocalService("workflow")
331 # Deleting the existing instance of workflow for SO
332 wf_service.trg_delete(uid, 'sale.order', inv_id, cr)
333 wf_service.trg_create(uid, 'sale.order', inv_id, cr)
334 for (id,name) in self.name_get(cr, uid, id):
335 message = _('Sale order ') + " '" + name + "' "+ _("is in draft state")
336 self.log(cr, uid, id, message)
339 def onchange_partner_id(self, cr, uid, ids, part):
341 return {'value': {'partner_invoice_id': False, 'partner_shipping_id': False, 'partner_order_id': False, 'payment_term': False, 'fiscal_position': False}}
343 addr = self.pool.get('res.partner').address_get(cr, uid, [part], ['delivery', 'invoice', 'contact'])
344 part = self.pool.get('res.partner').browse(cr, uid, part)
345 pricelist = part.property_product_pricelist and part.property_product_pricelist.id or False
346 payment_term = part.property_payment_term and part.property_payment_term.id or False
347 fiscal_position = part.property_account_position and part.property_account_position.id or False
348 dedicated_salesman = part.user_id and part.user_id.id or uid
351 'partner_invoice_id': addr['invoice'],
352 'partner_order_id': addr['contact'],
353 'partner_shipping_id': addr['delivery'],
354 'payment_term': payment_term,
355 'fiscal_position': fiscal_position,
356 'user_id': dedicated_salesman,
360 val['pricelist_id'] = pricelist
362 return {'value': val}
364 def shipping_policy_change(self, cr, uid, ids, policy, context=None):
370 if policy == 'prepaid':
372 elif policy == 'picking':
373 inv_qty = 'procurement'
374 return {'value': {'invoice_quantity': inv_qty}}
376 def write(self, cr, uid, ids, vals, context=None):
379 if 'order_policy' in vals:
380 if vals['order_policy'] == 'prepaid':
381 vals.update({'invoice_quantity': 'order'})
382 elif vals['order_policy'] == 'picking':
383 vals.update({'invoice_quantity': 'procurement'})
384 return super(sale_order, self).write(cr, uid, ids, vals, context=context)
386 def create(self, cr, uid, vals, context=None):
389 if 'order_policy' in vals:
390 if vals['order_policy'] == 'prepaid':
391 vals.update({'invoice_quantity': 'order'})
392 if vals['order_policy'] == 'picking':
393 vals.update({'invoice_quantity': 'procurement'})
394 return super(sale_order, self).create(cr, uid, vals, context=context)
396 def button_dummy(self, cr, uid, ids, context=None):
401 #FIXME: the method should return the list of invoices created (invoice_ids)
402 # and not the id of the last invoice created (res). The problem is that we
403 # cannot change it directly since the method is called by the sale order
404 # workflow and I suppose it expects a single id...
405 def _inv_get(self, cr, uid, order, context=None):
410 def _make_invoice(self, cr, uid, order, lines, context=None):
413 a = order.partner_id.property_account_receivable.id
414 if order.payment_term:
415 pay_term = order.payment_term.id
418 for preinv in order.invoice_ids:
419 if preinv.state not in ('cancel',):
420 for preline in preinv.invoice_line:
421 inv_line_id = self.pool.get('account.invoice.line').copy(cr, uid, preline.id, {'invoice_id': False, 'price_unit': -preline.price_unit})
422 lines.append(inv_line_id)
423 journal_obj = self.pool.get('account.journal')
424 journal_ids = journal_obj.search(cr, uid, [('type', '=','sale'),('company_id', '=', order.company_id.id)], limit=1)
426 raise osv.except_osv(_('Error !'),
427 _('There is no sale journal defined for this company: "%s" (id:%d)') % (order.company_id.name, order.company_id.id))
430 'name': order.client_order_ref or order.name,
431 'origin': order.name,
432 'type': 'out_invoice',
433 'reference': "P%dSO%d" % (order.partner_id.id, order.id),
435 'partner_id': order.partner_id.id,
436 'journal_id': journal_ids[0],
437 'address_invoice_id': order.partner_invoice_id.id,
438 'address_contact_id': order.partner_order_id.id,
439 'invoice_line': [(6, 0, lines)],
440 'currency_id': order.pricelist_id.currency_id.id,
441 'comment': order.note,
442 'payment_term': pay_term,
443 'fiscal_position': order.fiscal_position.id or order.partner_id.property_account_position.id,
444 'date_invoice' : context.get('date_invoice',False),
445 'company_id' : order.company_id.id,
446 'user_id':order.user_id and order.user_id.id or False
448 inv_obj = self.pool.get('account.invoice')
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):
463 # If date was specified, use it as date invoiced, usefull when invoices are generated this month and put the
464 # last day of the last month as invoice date
466 context['date_inv'] = date_inv
467 for o in self.browse(cr, uid, ids):
469 for line in o.order_line:
470 if (line.state in states) and not line.invoiced:
471 lines.append(line.id)
472 created_lines = self.pool.get('sale.order.line').invoice_line_create(cr, uid, lines)
474 invoices.setdefault(o.partner_id.id, []).append((o, created_lines))
477 for o in self.browse(cr, uid, ids):
478 for i in o.invoice_ids:
479 if i.state == 'draft':
481 picking_obj = self.pool.get('stock.picking')
482 for val in invoices.values():
484 res = self._make_invoice(cr, uid, val[0][0], reduce(lambda x,y: x + y, [l for o,l in val], []), context=context)
486 self.write(cr, uid, [o.id], {'state': 'progress'})
487 if o.order_policy == 'picking':
488 picking_obj.write(cr, uid, map(lambda x: x.id, o.picking_ids), {'invoice_state': 'invoiced'})
489 cr.execute('insert into sale_order_invoice_rel (order_id,invoice_id) values (%s,%s)', (o.id, res))
491 for order, il in val:
492 res = self._make_invoice(cr, uid, order, il, context=context)
493 invoice_ids.append(res)
494 self.write(cr, uid, [order.id], {'state': 'progress'})
495 if order.order_policy == 'picking':
496 picking_obj.write(cr, uid, map(lambda x: x.id, order.picking_ids), {'invoice_state': 'invoiced'})
497 cr.execute('insert into sale_order_invoice_rel (order_id,invoice_id) values (%s,%s)', (order.id, res))
500 def action_invoice_cancel(self, cr, uid, ids, context=None):
503 for sale in self.browse(cr, uid, ids, context=context):
504 for line in sale.order_line:
506 for iline in line.invoice_lines:
507 if iline.invoice_id and iline.invoice_id.state == 'cancel':
511 self.pool.get('sale.order.line').write(cr, uid, [line.id], {'invoiced': invoiced})
512 self.write(cr, uid, ids, {'state': 'invoice_except', 'invoice_ids': False})
515 def action_invoice_end(self, cr, uid, ids, context=None):
519 for order in self.browse(cr, uid, ids, context=context):
520 for line in order.order_line:
521 if line.state == 'exception':
522 self.pool.get('sale.order.line').write(cr, uid, [line.id], {'state': 'confirmed'}, context=context)
524 if order.state == 'invoice_except':
525 self.write(cr, uid, [order.id], {'state' : 'progress'}, context=context)
529 def action_cancel(self, cr, uid, ids, context=None):
533 sale_order_line_obj = self.pool.get('sale.order.line')
534 for sale in self.browse(cr, uid, ids, context=context):
535 for pick in sale.picking_ids:
536 if pick.state not in ('draft', 'cancel'):
537 raise osv.except_osv(
538 _('Could not cancel sale order !'),
539 _('You must first cancel all picking attached to this sale order.'))
540 for r in self.read(cr, uid, ids, ['picking_ids']):
541 for pick in r['picking_ids']:
542 wf_service = netsvc.LocalService("workflow")
543 wf_service.trg_validate(uid, 'stock.picking', pick, 'button_cancel', cr)
544 for inv in sale.invoice_ids:
545 if inv.state not in ('draft', 'cancel'):
546 raise osv.except_osv(
547 _('Could not cancel this sale order !'),
548 _('You must first cancel all invoices attached to this sale order.'))
549 for r in self.read(cr, uid, ids, ['invoice_ids']):
550 for inv in r['invoice_ids']:
551 wf_service = netsvc.LocalService("workflow")
552 wf_service.trg_validate(uid, 'account.invoice', inv, 'invoice_cancel', cr)
553 sale_order_line_obj.write(cr, uid, [l.id for l in sale.order_line],
555 self.write(cr, uid, ids, {'state': 'cancel'})
556 for line in sale.order_line:
557 message = _('Sale order') + " '" + sale.name + "' "+ _("created on")+" '" +sale.create_date + _(" is cancelled")
558 self.log(cr, uid, id, message)
561 def action_wait(self, cr, uid, ids, *args):
563 product_obj=self.pool.get('product.product')
564 for o in self.browse(cr, uid, ids):
565 if (o.order_policy == 'manual'):
566 self.write(cr, uid, [o.id], {'state': 'manual', 'date_confirm': time.strftime('%Y-%m-%d')})
568 self.write(cr, uid, [o.id], {'state': 'progress', 'date_confirm': time.strftime('%Y-%m-%d')})
569 self.pool.get('sale.order.line').button_confirm(cr, uid, [x.id for x in o.order_line])
570 for (id,name) in self.name_get(cr, uid, ids):
571 for line in o.order_line:
572 product.append(line.product_id.default_code)
573 params = ', '.join(map(lambda x : str(x),product))
574 message = _('Sale order ') + " '" + o.name + "' "+ _("created on")+" '" +o.create_date + "' "+_("for")+" '" +params + "' "+_("is confirmed")
575 self.log(cr, uid, id, message)
578 def procurement_lines_get(self, cr, uid, ids, *args):
580 for order in self.browse(cr, uid, ids, context={}):
581 for line in order.order_line:
582 if line.procurement_id:
583 res.append(line.procurement_id.id)
586 # if mode == 'finished':
587 # returns True if all lines are done, False otherwise
588 # if mode == 'canceled':
589 # returns True if there is at least one canceled line, False otherwise
590 def test_state(self, cr, uid, ids, mode, *args):
591 assert mode in ('finished', 'canceled'), _("invalid mode for test_state")
596 write_cancel_ids = []
597 for order in self.browse(cr, uid, ids, context={}):
598 for line in order.order_line:
599 if (not line.procurement_id) or (line.procurement_id.state=='done'):
600 if line.state != 'done':
601 write_done_ids.append(line.id)
604 if line.procurement_id:
605 if (line.procurement_id.state == 'cancel'):
607 if line.state != 'exception':
608 write_cancel_ids.append(line.id)
612 self.pool.get('sale.order.line').write(cr, uid, write_done_ids, {'state': 'done'})
614 self.pool.get('sale.order.line').write(cr, uid, write_cancel_ids, {'state': 'exception'})
616 if mode == 'finished':
618 elif mode == 'canceled':
624 def action_ship_create(self, cr, uid, ids, *args):
626 company = self.pool.get('res.users').browse(cr, uid, uid).company_id
627 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 - relativedelta(company.security_lead)).strftime('%Y-%m-%d %H:%M:%S')
634 if line.state == 'done':
636 if line.product_id and line.product_id.product_tmpl_id.type in ('product', 'consu'):
637 location_id = order.shop_id.warehouse_id.lot_stock_id.id
639 loc_dest_id = order.partner_id.property_stock_customer.id
640 pick_name = self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.out')
641 picking_id = self.pool.get('stock.picking').create(cr, uid, {
643 'origin': order.name,
646 'move_type': order.picking_policy,
648 'address_id': order.partner_shipping_id.id,
650 'invoice_state': (order.order_policy=='picking' and '2binvoiced') or 'none',
651 'company_id': order.company_id.id,
654 move_id = self.pool.get('stock.move').create(cr, uid, {
655 'name': line.name[:64],
656 'picking_id': picking_id,
657 'product_id': line.product_id.id,
658 'date_planned': date_planned,
659 'product_qty': line.product_uom_qty,
660 'product_uom': line.product_uom.id,
661 'product_uos_qty': line.product_uos_qty,
662 'product_uos': (line.product_uos and line.product_uos.id)\
663 or line.product_uom.id,
664 'product_packaging': line.product_packaging.id,
665 'address_id': line.address_allotment_id.id or order.partner_shipping_id.id,
666 'location_id': location_id,
667 'location_dest_id': output_id,
668 'sale_line_id': line.id,
669 'tracking_id': False,
673 'company_id': order.company_id.id,
675 proc_id = self.pool.get('procurement.order').create(cr, uid, {
677 'origin': order.name,
678 'date_planned': date_planned,
679 'product_id': line.product_id.id,
680 'product_qty': line.product_uom_qty,
681 'product_uom': line.product_uom.id,
682 'product_uos_qty': (line.product_uos and line.product_uos_qty)\
683 or line.product_uom_qty,
684 'product_uos': (line.product_uos and line.product_uos.id)\
685 or line.product_uom.id,
686 'location_id': order.shop_id.warehouse_id.lot_stock_id.id,
687 'procure_method': line.type,
689 'property_ids': [(6, 0, [x.id for x in line.property_ids])],
690 'company_id': order.company_id.id,
692 wf_service = netsvc.LocalService("workflow")
693 wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_confirm', cr)
694 self.pool.get('sale.order.line').write(cr, uid, [line.id], {'procurement_id': proc_id})
695 elif line.product_id and line.product_id.product_tmpl_id.type == 'service':
696 proc_id = self.pool.get('procurement.order').create(cr, uid, {
698 'origin': order.name,
699 'date_planned': date_planned,
700 'product_id': line.product_id.id,
701 'product_qty': line.product_uom_qty,
702 'product_uom': line.product_uom.id,
703 'location_id': order.shop_id.warehouse_id.lot_stock_id.id,
704 'procure_method': line.type,
705 'property_ids': [(6, 0, [x.id for x in line.property_ids])],
706 'company_id': order.company_id.id,
708 self.pool.get('sale.order.line').write(cr, uid, [line.id], {'procurement_id': proc_id})
709 wf_service = netsvc.LocalService("workflow")
710 wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_confirm', cr)
713 # No procurement because no product in the sale.order.line.
719 wf_service = netsvc.LocalService("workflow")
720 wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
722 if order.state == 'shipping_except':
723 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 self.write(cr, uid, [order.id], val)
734 def action_ship_end(self, cr, uid, ids, context=None):
737 for order in self.browse(cr, uid, ids, context=context):
738 val = {'shipped': True}
739 if order.state == 'shipping_except':
740 val['state'] = 'progress'
741 if (order.order_policy == 'manual'):
742 for line in order.order_line:
743 if (not line.invoiced) and (line.state not in ('cancel', 'draft')):
744 val['state'] = 'manual'
746 for line in order.order_line:
748 if line.state == 'exception':
749 towrite.append(line.id)
751 self.pool.get('sale.order.line').write(cr, uid, towrite, {'state': 'done'}, context=context)
752 self.write(cr, uid, [order.id], val)
755 def _log_event(self, cr, uid, ids, factor=0.7, name='Open Order'):
756 invs = self.read(cr, uid, ids, ['date_order', 'partner_id', 'amount_untaxed'])
758 part = inv['partner_id'] and inv['partner_id'][0]
759 pr = inv['amount_untaxed'] or 0.0
760 partnertype = 'customer'
763 'name': 'Order: '+name,
765 'description': 'Order '+str(inv['id']),
768 'date': time.strftime('%Y-%m-%d'),
771 'partner_type': partnertype,
773 'planned_revenue': pr,
777 self.pool.get('res.partner.event').create(cr, uid, event)
779 def has_stockable_products(self, cr, uid, ids, *args):
780 for order in self.browse(cr, uid, ids):
781 for order_line in order.order_line:
782 if order_line.product_id and order_line.product_id.product_tmpl_id.type in ('product', 'consu'):
787 # TODO add a field price_unit_uos
788 # - update it on change product and unit price
789 # - use it in report if there is a uos
790 class sale_order_line(osv.osv):
791 def _amount_line(self, cr, uid, ids, field_name, arg, context=None):
793 context = context or {}
794 tax_obj = self.pool.get('account.tax')
795 cur_obj = self.pool.get('res.currency')
796 for line in self.browse(cr, uid, ids, context=context):
797 price = line.price_unit * line.product_uom_qty * (1 - (line.discount or 0.0) / 100.0)
798 taxes = tax_obj.compute_all(cr, uid, line.tax_id, price, line.product_uom_qty)
799 cur = line.order_id.pricelist_id.currency_id
800 res[line.id] = cur_obj.round(cr, uid, cur, taxes['total'])
803 def _number_packages(self, cr, uid, ids, field_name, arg, context=None):
807 for line in self.browse(cr, uid, ids, context=context):
809 res[line.id] = int((line.product_uom_qty+line.product_packaging.qty-0.0001) / line.product_packaging.qty)
814 _name = 'sale.order.line'
815 _description = 'Sale Order Line'
817 'order_id': fields.many2one('sale.order', 'Order Reference', required=True, ondelete='cascade', select=True, readonly=True, states={'draft':[('readonly',False)]}),
818 'name': fields.char('Description', size=256, required=True, select=True, readonly=True, states={'draft':[('readonly',False)]}),
819 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of sale order lines."),
820 '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)]}),
821 'product_id': fields.many2one('product.product', 'Product', domain=[('sale_ok', '=', True)], change_default=True),
822 'invoice_lines': fields.many2many('account.invoice.line', 'sale_order_line_invoice_rel', 'order_line_id', 'invoice_id', 'Invoice Lines', readonly=True),
823 'invoiced': fields.boolean('Invoiced', readonly=True),
824 'procurement_id': fields.many2one('procurement.order', 'Procurement'),
825 'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Sale Price'), readonly=True, states={'draft':[('readonly',False)]}),
826 'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal', digits_compute= dp.get_precision('Sale Price')),
827 'tax_id': fields.many2many('account.tax', 'sale_order_tax', 'order_line_id', 'tax_id', 'Taxes', readonly=True, states={'draft':[('readonly',False)]}),
828 'type': fields.selection([('make_to_stock', 'from stock'), ('make_to_order', 'on order')], 'Procurement Method', required=True, readonly=True, states={'draft':[('readonly',False)]}),
829 'property_ids': fields.many2many('mrp.property', 'sale_order_line_property_rel', 'order_id', 'property_id', 'Properties', readonly=True, states={'draft':[('readonly',False)]}),
830 'address_allotment_id': fields.many2one('res.partner.address', 'Allotment Partner'),
831 'product_uom_qty': fields.float('Quantity (UoM)', digits=(16, 2), required=True, readonly=True, states={'draft':[('readonly',False)]}),
832 'product_uom': fields.many2one('product.uom', 'Unit of Measure ', required=True, readonly=True, states={'draft':[('readonly',False)]}),
833 'product_uos_qty': fields.float('Quantity (UoS)', readonly=True, states={'draft':[('readonly',False)]}),
834 'product_uos': fields.many2one('product.uom', 'Product UoS'),
835 'product_packaging': fields.many2one('product.packaging', 'Packaging'),
836 'move_ids': fields.one2many('stock.move', 'sale_line_id', 'Inventory Moves', readonly=True),
837 'discount': fields.float('Discount (%)', digits=(16, 2), readonly=True, states={'draft':[('readonly',False)]}),
838 'number_packages': fields.function(_number_packages, method=True, type='integer', string='Number Packages'),
839 'notes': fields.text('Notes', translate=True),
840 'th_weight': fields.float('Weight'),
841 'state': fields.selection([('draft', 'Draft'),('confirmed', 'Confirmed'),('done', 'Done'),('cancel', 'Cancelled'),('exception', 'Exception')], 'State', required=True, readonly=True,
842 help=' * The \'Draft\' state is set automatically when sale order in draft state. \
843 \n* The \'Confirmed\' state is set automatically when sale order in confirm state. \
844 \n* The \'Exception\' state is set automatically when sale order is set as exception. \
845 \n* The \'Done\' state is set automatically when sale order is set as done. \
846 \n* The \'Cancelled\' state is set automatically when user cancel sale order.'),
847 'order_partner_id': fields.related('order_id', 'partner_id', type='many2one', relation='res.partner', string='Customer'),
848 'salesman_id':fields.related('order_id','user_id',type='many2one',relation='res.users',string='Salesman'),
849 'company_id': fields.related('order_id','company_id',type='many2one',relation='res.company',string='Company',store=True),
851 _order = 'sequence, id'
853 'discount': lambda *a: 0.0,
854 'delay': lambda *a: 0.0,
855 'product_uom_qty': lambda *a: 1,
856 'product_uos_qty': lambda *a: 1,
857 'sequence': lambda *a: 10,
858 'invoiced': lambda *a: 0,
859 'state': lambda *a: 'draft',
860 'type': lambda *a: 'make_to_stock',
861 'product_packaging': lambda *a: False
864 def invoice_line_create(self, cr, uid, ids, context=None):
867 def _get_line_qty(line):
868 if (line.order_id.invoice_quantity=='order') or not line.procurement_id:
870 return line.product_uos_qty or 0.0
871 return line.product_uom_qty
873 return self.pool.get('procurement.order').quantity_get(cr, uid,
874 line.procurement_id.id, context=context)
876 def _get_line_uom(line):
877 if (line.order_id.invoice_quantity=='order') or not line.procurement_id:
879 return line.product_uos.id
880 return line.product_uom.id
882 return self.pool.get('procurement.order').uom_get(cr, uid,
883 line.procurement_id.id, context=context)
887 for line in self.browse(cr, uid, ids, context=context):
888 if not line.invoiced:
890 a = line.product_id.product_tmpl_id.property_account_income.id
892 a = line.product_id.categ_id.property_account_income_categ.id
894 raise osv.except_osv(_('Error !'),
895 _('There is no income account defined ' \
896 'for this product: "%s" (id:%d)') % \
897 (line.product_id.name, line.product_id.id,))
899 a = self.pool.get('ir.property').get(cr, uid,
900 'property_account_income_categ', 'product.category',
902 uosqty = _get_line_qty(line)
903 uos_id = _get_line_uom(line)
906 pu = round(line.price_unit * line.product_uom_qty / uosqty,
907 self.pool.get('decimal.precision').precision_get(cr, uid, 'Sale Price'))
908 fpos = line.order_id.fiscal_position or False
909 a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
911 raise osv.except_osv(_('Error !'),
912 _('There is no income category account defined in default Properties for Product Category or Fiscal Position is not defined !'))
913 inv_id = self.pool.get('account.invoice.line').create(cr, uid, {
915 'origin': line.order_id.name,
919 'discount': line.discount,
921 'product_id': line.product_id.id or False,
922 'invoice_line_tax_id': [(6, 0, [x.id for x in line.tax_id])],
924 'account_analytic_id': line.order_id.project_id and line.order_id.project_id.id or False,
926 cr.execute('insert into sale_order_line_invoice_rel (order_line_id,invoice_id) values (%s,%s)', (line.id, inv_id))
927 self.write(cr, uid, [line.id], {'invoiced': True})
929 sales[line.order_id.id] = True
930 create_ids.append(inv_id)
932 # Trigger workflow events
933 wf_service = netsvc.LocalService("workflow")
934 for sid in sales.keys():
935 wf_service.trg_write(uid, 'sale.order', sid, cr)
938 def button_cancel(self, cr, uid, ids, context=None):
941 for line in self.browse(cr, uid, ids, context=context):
943 raise osv.except_osv(_('Invalid action !'), _('You cannot cancel a sale order line that has already been invoiced !'))
944 message = _('Sale order line') + " '" + line.name + "' "+_("is cancelled")
945 self.log(cr, uid, id, message)
946 return self.write(cr, uid, ids, {'state': 'cancel'})
948 def button_confirm(self, cr, uid, ids, context=None):
951 for (id,name) in self.name_get(cr, uid, ids):
952 message = _('Sale order line') + " '" + name + "' "+ _("is confirmed")
953 self.log(cr, uid, id, message)
954 return self.write(cr, uid, ids, {'state': 'confirmed'})
956 def button_done(self, cr, uid, ids, context=None):
959 wf_service = netsvc.LocalService("workflow")
960 res = self.write(cr, uid, ids, {'state': 'done'})
961 for line in self.browse(cr, uid, ids, context=context):
962 wf_service.trg_write(uid, 'sale.order', line.order_id.id, cr)
963 message = _('Sale order line') + " '" + line.name + "' "+_("is done")
964 self.log(cr, uid, id, message)
967 def uos_change(self, cr, uid, ids, product_uos, product_uos_qty=0, product_id=None):
968 product_obj = self.pool.get('product.product')
970 return {'value': {'product_uom': product_uos,
971 'product_uom_qty': product_uos_qty}, 'domain': {}}
973 product = product_obj.browse(cr, uid, product_id)
975 'product_uom': product.uom_id.id,
977 # FIXME must depend on uos/uom of the product and not only of the coeff.
980 'product_uom_qty': product_uos_qty / product.uos_coeff,
981 'th_weight': product_uos_qty / product.uos_coeff * product.weight
983 except ZeroDivisionError:
985 return {'value': value}
987 def copy_data(self, cr, uid, id, default=None, context=None):
992 default.update({'state': 'draft', 'move_ids': [], 'invoiced': False, 'invoice_lines': []})
993 return super(sale_order_line, self).copy_data(cr, uid, id, default, context=context)
995 def product_id_change(self, cr, uid, ids, pricelist, product, qty=0,
996 uom=False, qty_uos=0, uos=False, name='', partner_id=False,
997 lang=False, update_tax=True, date_order=False, packaging=False, fiscal_position=False, flag=False):
999 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.'))
1001 product_uom_obj = self.pool.get('product.uom')
1002 partner_obj = self.pool.get('res.partner')
1003 product_obj = self.pool.get('product.product')
1005 lang = partner_obj.browse(cr, uid, partner_id).lang
1006 context = {'lang': lang, 'partner_id': partner_id}
1009 return {'value': {'th_weight': 0, 'product_packaging': False,
1010 'product_uos_qty': qty}, 'domain': {'product_uom': [],
1014 date_order = time.strftime('%Y-%m-%d')
1017 product_obj = product_obj.browse(cr, uid, product, context=context)
1018 if not packaging and product_obj.packaging:
1019 packaging = product_obj.packaging[0].id
1020 result['product_packaging'] = packaging
1023 default_uom = product_obj.uom_id and product_obj.uom_id.id
1024 pack = self.pool.get('product.packaging').browse(cr, uid, packaging, context=context)
1025 q = product_uom_obj._compute_qty(cr, uid, uom, pack.qty, default_uom)
1026 # qty = qty - qty % q + q
1027 if qty and (q and not (qty % q) == 0):
1031 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)
1032 warn_msg = warn_msg + "\n\n" + _("EAN: ") + str(ean) + _(" Quantity: ") + str(qty_pack) + _(" Type of ul: ") + str(type_ul.name)
1034 'title': _('Picking Information !'),
1037 result['product_uom_qty'] = qty
1040 uom2 = product_uom_obj.browse(cr, uid, uom)
1041 if product_obj.uom_id.category_id.id != uom2.category_id.id:
1045 if product_obj.uos_id:
1046 uos2 = product_uom_obj.browse(cr, uid, uos)
1047 if product_obj.uos_id.category_id.id != uos2.category_id.id:
1051 if product_obj.description_sale:
1052 result['notes'] = product_obj.description_sale
1053 fpos = fiscal_position and self.pool.get('account.fiscal.position').browse(cr, uid, fiscal_position) or False
1054 if update_tax: #The quantity only have changed
1055 result['delay'] = (product_obj.sale_delay or 0.0)
1056 partner = partner_obj.browse(cr, uid, partner_id)
1057 result['tax_id'] = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, product_obj.taxes_id)
1058 result.update({'type': product_obj.procure_method})
1061 result['name'] = self.pool.get('product.product').name_get(cr, uid, [product_obj.id], context=context)[0][1]
1063 if (not uom) and (not uos):
1064 result['product_uom'] = product_obj.uom_id.id
1065 if product_obj.uos_id:
1066 result['product_uos'] = product_obj.uos_id.id
1067 result['product_uos_qty'] = qty * product_obj.uos_coeff
1068 uos_category_id = product_obj.uos_id.category_id.id
1070 result['product_uos'] = False
1071 result['product_uos_qty'] = qty
1072 uos_category_id = False
1073 result['th_weight'] = qty * product_obj.weight
1074 domain = {'product_uom':
1075 [('category_id', '=', product_obj.uom_id.category_id.id)],
1077 [('category_id', '=', uos_category_id)]}
1079 elif uos and not uom: # only happens if uom is False
1080 result['product_uom'] = product_obj.uom_id and product_obj.uom_id.id
1081 result['product_uom_qty'] = qty_uos / product_obj.uos_coeff
1082 result['th_weight'] = result['product_uom_qty'] * product_obj.weight
1083 elif uom: # whether uos is set or not
1084 default_uom = product_obj.uom_id and product_obj.uom_id.id
1085 q = product_uom_obj._compute_qty(cr, uid, uom, qty, default_uom)
1086 if product_obj.uos_id:
1087 result['product_uos'] = product_obj.uos_id.id
1088 result['product_uos_qty'] = qty * product_obj.uos_coeff
1090 result['product_uos'] = False
1091 result['product_uos_qty'] = qty
1092 result['th_weight'] = q * product_obj.weight # Round the quantity up
1098 'title': 'No Pricelist !',
1100 'You have to select a pricelist or a customer in the sale form !\n'
1101 'Please set one before choosing a product.'
1104 price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist],
1105 product, qty or 1.0, partner_id, {
1111 'title': 'No valid pricelist line found !',
1113 "Couldn't find a pricelist line matching this product and quantity.\n"
1114 "You have to change either the product, the quantity or the pricelist."
1117 result.update({'price_unit': price})
1118 return {'value': result, 'domain': domain, 'warning': warning}
1120 def product_uom_change(self, cursor, user, ids, pricelist, product, qty=0,
1121 uom=False, qty_uos=0, uos=False, name='', partner_id=False,
1122 lang=False, update_tax=True, date_order=False):
1123 res = self.product_id_change(cursor, user, ids, pricelist, product,
1124 qty=qty, uom=uom, qty_uos=qty_uos, uos=uos, name=name,
1125 partner_id=partner_id, lang=lang, update_tax=update_tax,
1126 date_order=date_order)
1127 if 'product_uom' in res['value']:
1128 del res['value']['product_uom']
1130 res['value']['price_unit'] = 0.0
1133 def unlink(self, cr, uid, ids, context=None):
1136 """Allows to delete sale order lines in draft,cancel states"""
1137 for rec in self.browse(cr, uid, ids, context=context):
1138 if rec.state not in ['draft', 'cancel']:
1139 raise osv.except_osv(_('Invalid action !'), _('Cannot delete a sale order line which is %s !')%(rec.state,))
1140 return super(sale_order_line, self).unlink(cr, uid, ids, context=context)
1145 class sale_config_picking_policy(osv.osv_memory):
1146 _name = 'sale.config.picking_policy'
1147 _inherit = 'res.config'
1150 'name': fields.char('Name', size=64),
1151 'picking_policy': fields.selection([
1152 ('direct', 'Direct Delivery'),
1153 ('one', 'All at Once')
1154 ], 'Picking Default Policy', required=True),
1155 'order_policy': fields.selection([
1156 ('manual', 'Invoice Based on Sales Orders'),
1157 ('picking', 'Invoice Based on Deliveries'),
1158 ], 'Shipping Default Policy', required=True,
1159 help="You can generate invoices based on sales orders or based on shippings."),
1160 'step': fields.selection([
1161 ('one', 'Delivery Order Only'),
1162 ('two', 'Picking List & Delivery Order')
1163 ], 'Steps To Deliver a Sale Order', required=True,
1164 help="By default, Open ERP is able to manage complex routing and paths "\
1165 "of products in your warehouse and partner locations. This will configure "\
1166 "the most common and simple methods to deliver products to the customer "\
1167 "in one or two operations by the worker.")
1170 'picking_policy': lambda *a: 'direct',
1171 'order_policy': lambda *a: 'manual',
1172 'step': lambda *a: 'one'
1175 def execute(self, cr, uid, ids, context=None):
1178 for o in self.browse(cr, uid, ids, context=context):
1179 ir_values_obj = self.pool.get('ir.values')
1180 ir_values_obj.set(cr, uid, 'default', False, 'picking_policy', ['sale.order'], o.picking_policy)
1181 ir_values_obj.set(cr, uid, 'default', False, 'order_policy', ['sale.order'], o.order_policy)
1184 md = self.pool.get('ir.model.data')
1185 group_id = md._get_id(cr, uid, 'base', 'group_no_one')
1186 group_id = md.browse(cr, uid, group_id, context=context).res_id
1187 menu_id = md._get_id(cr, uid, 'stock', 'menu_action_picking_tree_delivery')
1188 menu_id = md.browse(cr, uid, menu_id, context=context).res_id
1189 self.pool.get('ir.ui.menu').write(cr, uid, [menu_id], {'groups_id': [(6, 0, [group_id])]})
1191 location_id = md._get_id(cr, uid, 'stock', 'stock_location_output')
1192 location_id = md.browse(cr, uid, location_id, context=context).res_id
1193 self.pool.get('stock.location').write(cr, uid, [location_id], {'chained_auto_packing': 'transparent'})
1194 sale_config_picking_policy()