1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
22 from datetime import datetime, timedelta
23 from dateutil.relativedelta import relativedelta
26 from osv import fields, osv
27 from tools.translate import _
28 import decimal_precision as dp
31 class sale_shop(osv.osv):
33 _description = "Sale Shop"
35 'name': fields.char('Shop Name', size=64, required=True),
36 'payment_default_id': fields.many2one('account.payment.term', 'Default Payment Term', required=True),
37 'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse'),
38 'pricelist_id': fields.many2one('product.pricelist', 'Pricelist'),
39 'project_id': fields.many2one('account.analytic.account', 'Analytic Account', domain=[('parent_id', '!=', False)]),
40 'company_id': fields.many2one('res.company', 'Company', required=False),
43 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'sale.shop', context=c),
48 def _incoterm_get(self, cr, uid, context=None):
51 cr.execute('select code, code||\', \'||name from stock_incoterms where active')
54 class sale_order(osv.osv):
56 _description = "Sale Order"
58 def copy(self, cr, uid, id, default=None, context=None):
68 'date_confirm': False,
69 'name': self.pool.get('ir.sequence').get(cr, uid, 'sale.order'),
71 return super(sale_order, self).copy(cr, uid, id, default, context=context)
73 def _amount_line_tax(self, cr, uid, line, context=None):
77 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 val += c.get('amount', 0.0)
81 def _amount_all(self, cr, uid, ids, field_name, arg, context=None):
82 cur_obj = self.pool.get('res.currency')
86 for order in self.browse(cr, uid, ids, context=context):
88 'amount_untaxed': 0.0,
93 cur = order.pricelist_id.currency_id
94 for line in order.order_line:
95 val1 += line.price_subtotal
96 val += self._amount_line_tax(cr, uid, line, context=context)
97 res[order.id]['amount_tax'] = cur_obj.round(cr, uid, cur, val)
98 res[order.id]['amount_untaxed'] = cur_obj.round(cr, uid, cur, val1)
99 res[order.id]['amount_total'] = res[order.id]['amount_untaxed'] + res[order.id]['amount_tax']
103 def _picked_rate(self, cr, uid, ids, name, arg, context=None):
112 p.sale_id, sum(m.product_qty), mp.state as mp_state
116 stock_picking p on (p.id=m.picking_id)
118 procurement_order mp on (mp.move_id=m.id)
120 p.sale_id IN %s GROUP BY mp.state, p.sale_id''', (tuple(ids),))
121 for oid, nbr, mp_state in cr.fetchall():
122 if mp_state == 'cancel':
124 if mp_state == 'done':
125 res[oid][0] += nbr or 0.0
126 res[oid][1] += nbr or 0.0
128 res[oid][1] += nbr or 0.0
133 res[r] = 100.0 * res[r][0] / res[r][1]
134 for order in self.browse(cr, uid, ids, context=context):
136 res[order.id] = 100.0
139 def _invoiced_rate(self, cursor, user, ids, name, arg, context=None):
143 for sale in self.browse(cursor, user, ids, context=context):
148 for invoice in sale.invoice_ids:
149 if invoice.state not in ('draft', 'cancel'):
150 tot += invoice.amount_untaxed
152 res[sale.id] = min(100.0, tot * 100.0 / (sale.amount_untaxed or 1.00))
157 def _invoiced(self, cursor, user, ids, name, arg, context=None):
161 for sale in self.browse(cursor, user, ids, context=context):
163 for invoice in sale.invoice_ids:
164 if invoice.state != 'paid':
167 if not sale.invoice_ids:
171 def _invoiced_search(self, cursor, user, obj, name, args, context=None):
181 clause += 'AND inv.state = \'paid\''
183 clause += 'AND inv.state <> \'paid\''
185 cursor.execute('SELECT rel.order_id ' \
186 'FROM sale_order_invoice_rel AS rel, account_invoice AS inv ' \
187 'WHERE rel.invoice_id = inv.id ' + clause)
188 res = cursor.fetchall()
190 cursor.execute('SELECT sale.id ' \
191 'FROM sale_order AS sale ' \
192 'WHERE sale.id NOT IN ' \
193 '(SELECT rel.order_id ' \
194 'FROM sale_order_invoice_rel AS rel)')
195 res.extend(cursor.fetchall())
197 return [('id', '=', 0)]
198 return [('id', 'in', [x[0] for x in res])]
200 def _get_order(self, cr, uid, ids, context=None):
204 for line in self.pool.get('sale.order.line').browse(cr, uid, ids, context=context):
205 result[line.order_id.id] = True
209 'name': fields.char('Order Reference', size=64, required=True,
210 readonly=True, states={'draft': [('readonly', False)]}, select=True),
211 'shop_id': fields.many2one('sale.shop', 'Shop', required=True, readonly=True, states={'draft': [('readonly', False)]}),
212 'origin': fields.char('Source document', size=64, help="Reference of the document that generated this sale order request."),
213 'client_order_ref': fields.char('Customer Reference', size=64),
214 'state': fields.selection([
215 ('draft', 'Quotation'),
216 ('waiting_date', 'Waiting Schedule'),
217 ('manual', 'Manual In Progress'),
218 ('progress', 'In Progress'),
219 ('shipping_except', 'Shipping Exception'),
220 ('invoice_except', 'Invoice Exception'),
222 ('cancel', 'Cancelled')
223 ], '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),
224 'date_order': fields.date('Ordered Date', required=True, readonly=True, states={'draft': [('readonly', False)]}),
225 'create_date': fields.date('Creation Date', readonly=True, help="Date on which sale order is created"),
226 'date_confirm': fields.date('Confirmation Date', readonly=True, help="Date on which sale order is confirmed"),
227 'user_id': fields.many2one('res.users', 'Salesman', states={'draft': [('readonly', False)]}, select=True),
228 'partner_id': fields.many2one('res.partner', 'Customer', readonly=True, states={'draft': [('readonly', False)]}, required=True, change_default=True, select=True),
229 '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"),
230 '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."),
231 '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"),
233 '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"),
234 'picking_policy': fields.selection([('direct', 'Partial Delivery'), ('one', 'Complete Delivery')],
235 '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?"""),
236 'order_policy': fields.selection([
237 ('prepaid', 'Payment Before Delivery'),
238 ('manual', 'Shipping & Manual Invoice'),
239 ('postpaid', 'Invoice on Order After Delivery'),
240 ('picking', 'Invoice from Delivery'),
241 ], 'Shipping Policy', required=True, readonly=True, states={'draft': [('readonly', False)]},
242 help="""The Shipping Policy is used to synchronise invoice and delivery operations.
243 - The 'Pay before delivery' choice will first generate the invoice and then generate the picking order after the payment of this invoice.
244 - 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.
245 - The 'Invoice on Order After Delivery' choice will generate the draft invoice based on sale order after all picking lists have been finished.
246 - The 'Invoice from the picking' choice is used to create an invoice during the picking process."""),
247 'pricelist_id': fields.many2one('product.pricelist', 'Pricelist', required=True, readonly=True, states={'draft': [('readonly', False)]}, help="Pricelist version for current sale order"),
248 'project_id': fields.many2one('account.analytic.account', 'Analytic Account', readonly=True, states={'draft': [('readonly', False)]}, help="The analytic account related to a sale order"),
250 'order_line': fields.one2many('sale.order.line', 'order_id', 'Order Lines', readonly=True, states={'draft': [('readonly', False)]}),
251 '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)."),
252 '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"),
253 'shipped': fields.boolean('Delivered', readonly=True, help="It indicates that the sale order has been delivered. This field is updated only after the scheduler have been launched !"),
254 'picked_rate': fields.function(_picked_rate, method=True, string='Picked', type='float'),
255 'invoiced_rate': fields.function(_invoiced_rate, method=True, string='Invoiced', type='float'),
256 'invoiced': fields.function(_invoiced, method=True, string='Paid',
257 fnct_search=_invoiced_search, type='boolean', help="It indicates that an invoice has been paid"),
258 'note': fields.text('Notes'),
260 'amount_untaxed': fields.function(_amount_all, method=True, digits_compute= dp.get_precision('Sale Price'), string='Untaxed Amount',
262 'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line'], 10),
263 'sale.order.line': (_get_order, ['price_unit', 'tax_id', 'discount', 'product_uom_qty'], 10),
265 multi='sums', help="The amount without tax"),
266 'amount_tax': fields.function(_amount_all, method=True, digits_compute= dp.get_precision('Sale Price'), string='Taxes',
268 'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line'], 10),
269 'sale.order.line': (_get_order, ['price_unit', 'tax_id', 'discount', 'product_uom_qty'], 10),
271 multi='sums', help="The tax amount"),
272 'amount_total': fields.function(_amount_all, method=True, digits_compute= dp.get_precision('Sale Price'), string='Total',
274 'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line'], 10),
275 'sale.order.line': (_get_order, ['price_unit', 'tax_id', 'discount', 'product_uom_qty'], 10),
277 multi='sums', help="The total amount"),
279 'invoice_quantity': fields.selection([('order', 'Ordered Quantities'), ('procurement', 'Shipped Quantities')], 'Invoice on', help="The sale order will automatically create the invoice proposition (draft invoice). Ordered and delivered quantities may not be the same. You have to choose if you want your invoice based on ordered or shipped quantities. If the product is a service, shipped quantities means hours spent on the associated tasks.", required=True),
280 'payment_term': fields.many2one('account.payment.term', 'Payment Term'),
281 'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position'),
282 'company_id': fields.related('shop_id','company_id',type='many2one',relation='res.company',string='Company',store=True)
285 'picking_policy': 'direct',
286 'date_order': time.strftime('%Y-%m-%d'),
287 'order_policy': 'manual',
289 'user_id': lambda obj, cr, uid, context: uid,
290 'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'sale.order'),
291 'invoice_quantity': 'order',
292 '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'],
293 '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'],
294 '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'],
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
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 = _("The sale order '%s' has been set in draft state.") %(name,)
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
349 'partner_invoice_id': addr['invoice'],
350 'partner_order_id': addr['contact'],
351 'partner_shipping_id': addr['delivery'],
352 'payment_term': payment_term,
353 'fiscal_position': fiscal_position,
354 'user_id': dedicated_salesman,
357 val['pricelist_id'] = pricelist
358 return {'value': val}
360 def shipping_policy_change(self, cr, uid, ids, policy, context=None):
366 if policy == 'prepaid':
368 elif policy == 'picking':
369 inv_qty = 'procurement'
370 return {'value': {'invoice_quantity': inv_qty}}
372 def write(self, cr, uid, ids, vals, context=None):
375 if vals.get('order_policy', False):
376 if vals['order_policy'] == 'prepaid':
377 vals.update({'invoice_quantity': 'order'})
378 elif vals['order_policy'] == 'picking':
379 vals.update({'invoice_quantity': 'procurement'})
380 return super(sale_order, self).write(cr, uid, ids, vals, context=context)
382 def create(self, cr, uid, vals, context=None):
385 if vals.get('order_policy', False):
386 if vals['order_policy'] == 'prepaid':
387 vals.update({'invoice_quantity': 'order'})
388 if vals['order_policy'] == 'picking':
389 vals.update({'invoice_quantity': 'procurement'})
390 return super(sale_order, self).create(cr, uid, vals, context=context)
392 def button_dummy(self, cr, uid, ids, context=None):
397 #FIXME: the method should return the list of invoices created (invoice_ids)
398 # and not the id of the last invoice created (res). The problem is that we
399 # cannot change it directly since the method is called by the sale order
400 # workflow and I suppose it expects a single id...
401 def _inv_get(self, cr, uid, order, context=None):
406 def _make_invoice(self, cr, uid, order, lines, context=None):
407 journal_obj = self.pool.get('account.journal')
408 inv_obj = self.pool.get('account.invoice')
409 obj_invoice_line = self.pool.get('account.invoice.line')
413 journal_ids = journal_obj.search(cr, uid, [('type', '=', 'sale'), ('company_id', '=', order.company_id.id)], limit=1)
415 raise osv.except_osv(_('Error !'),
416 _('There is no sale journal defined for this company: "%s" (id:%d)') % (order.company_id.name, order.company_id.id))
417 a = order.partner_id.property_account_receivable.id
418 pay_term = order.payment_term and order.payment_term.id or False
419 invoiced_sale_line_ids = self.pool.get('sale.order.line').search(cr, uid, [('order_id', '=', order.id), ('invoiced', '=', True)], context=context)
420 from_line_invoice_ids = []
421 for invoiced_sale_line_id in self.pool.get('sale.order.line').browse(cr, uid, invoiced_sale_line_ids, context=context):
422 for invoice_line_id in invoiced_sale_line_id.invoice_lines:
423 if invoice_line_id.invoice_id.id not in from_line_invoice_ids:
424 from_line_invoice_ids.append(invoice_line_id.invoice_id.id)
425 for preinv in order.invoice_ids:
426 if preinv.state not in ('cancel',) and preinv.id not in from_line_invoice_ids:
427 for preline in preinv.invoice_line:
428 inv_line_id = obj_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 manual_invoice(self, cr, uid, ids, context=None):
458 mod_obj = self.pool.get('ir.model.data')
459 wf_service = netsvc.LocalService("workflow")
463 for record in self.pool.get('sale.order').browse(cr, uid, id).invoice_ids:
464 inv_ids.add(record.id)
465 # inv_ids would have old invoices if any
467 wf_service.trg_validate(uid, 'sale.order', id, 'manual_invoice', cr)
468 for record in self.pool.get('sale.order').browse(cr, uid, id).invoice_ids:
469 inv_ids1.add(record.id)
470 inv_ids = list(inv_ids1.difference(inv_ids))
472 result = mod_obj._get_id(cr, uid, 'account', 'invoice_form')
473 res = mod_obj.read(cr, uid, result, ['res_id'])
475 'name': 'Customer Invoices',
478 'view_id': [res['res_id']],
479 'res_model': 'account.invoice',
480 'context': "{'type':'out_invoice'}",
481 'type': 'ir.actions.act_window',
484 'res_id': inv_ids and inv_ids[0] or False,
488 def action_invoice_create(self, cr, uid, ids, grouped=False, states=['confirmed', 'done', 'exception'], date_inv = False, context=None):
492 picking_obj = self.pool.get('stock.picking')
493 invoice = self.pool.get('account.invoice')
494 obj_sale_order_line = self.pool.get('sale.order.line')
497 # If date was specified, use it as date invoiced, usefull when invoices are generated this month and put the
498 # last day of the last month as invoice date
500 context['date_inv'] = date_inv
501 for o in self.browse(cr, uid, ids):
503 for line in o.order_line:
506 elif (line.state in states):
507 lines.append(line.id)
508 created_lines = obj_sale_order_line.invoice_line_create(cr, uid, lines)
510 invoices.setdefault(o.partner_id.id, []).append((o, created_lines))
512 for o in self.browse(cr, uid, ids):
513 for i in o.invoice_ids:
514 if i.state == 'draft':
516 for val in invoices.values():
518 res = self._make_invoice(cr, uid, val[0][0], reduce(lambda x, y: x + y, [l for o, l in val], []), context=context)
521 invoice_ref += o.name + '|'
522 self.write(cr, uid, [o.id], {'state': 'progress'})
523 if o.order_policy == 'picking':
524 picking_obj.write(cr, uid, map(lambda x: x.id, o.picking_ids), {'invoice_state': 'invoiced'})
525 cr.execute('insert into sale_order_invoice_rel (order_id,invoice_id) values (%s,%s)', (o.id, res))
526 invoice.write(cr, uid, [res], {'origin': invoice_ref, 'name': invoice_ref})
528 for order, il in val:
529 res = self._make_invoice(cr, uid, order, il, context=context)
530 invoice_ids.append(res)
531 self.write(cr, uid, [order.id], {'state': 'progress'})
532 if order.order_policy == 'picking':
533 picking_obj.write(cr, uid, map(lambda x: x.id, order.picking_ids), {'invoice_state': 'invoiced'})
534 cr.execute('insert into sale_order_invoice_rel (order_id,invoice_id) values (%s,%s)', (order.id, res))
537 def action_invoice_cancel(self, cr, uid, ids, context=None):
540 for sale in self.browse(cr, uid, ids):
541 for line in sale.order_line:
543 # Check if the line is invoiced (has asociated invoice
544 # lines from non-cancelled invoices).
547 for iline in line.invoice_lines:
548 if iline.invoice_id and iline.invoice_id.state != 'cancel':
551 # Update the line (only when needed)
552 if line.invoiced != invoiced:
553 self.pool.get('sale.order.line').write(cr, uid, [line.id], {'invoiced': invoiced}, context=context)
554 self.write(cr, uid, ids, {'state': 'invoice_except', 'invoice_ids': False}, context=context)
557 def action_invoice_end(self, cr, uid, ids, context=None):
558 for order in self.browse(cr, uid, ids, context=context):
560 # Update the sale order lines state (and invoiced flag).
562 for line in order.order_line:
565 # Check if the line is invoiced (has asociated invoice
566 # lines from non-cancelled invoices).
569 for iline in line.invoice_lines:
570 if iline.invoice_id and iline.invoice_id.state != 'cancel':
573 if line.invoiced != invoiced:
574 vals['invoiced'] = invoiced
575 # If the line was in exception state, now it gets confirmed.
576 if line.state == 'exception':
577 vals['state'] = 'confirmed'
578 # Update the line (only when needed).
580 self.pool.get('sale.order.line').write(cr, uid, [line.id], vals, context=context)
582 # Update the sale order state.
584 if order.state == 'invoice_except':
585 self.write(cr, uid, [order.id], {'state': 'progress'}, context=context)
588 def action_cancel(self, cr, uid, ids, context=None):
589 wf_service = netsvc.LocalService("workflow")
592 sale_order_line_obj = self.pool.get('sale.order.line')
593 for sale in self.browse(cr, uid, ids, context=context):
594 for pick in sale.picking_ids:
595 if pick.state not in ('draft', 'cancel'):
596 raise osv.except_osv(
597 _('Could not cancel sale order !'),
598 _('You must first cancel all picking attached to this sale order.'))
599 for r in self.read(cr, uid, ids, ['picking_ids']):
600 for pick in r['picking_ids']:
601 wf_service.trg_validate(uid, 'stock.picking', pick, 'button_cancel', cr)
602 for inv in sale.invoice_ids:
603 if inv.state not in ('draft', 'cancel'):
604 raise osv.except_osv(
605 _('Could not cancel this sale order !'),
606 _('You must first cancel all invoices attached to this sale order.'))
607 for r in self.read(cr, uid, ids, ['invoice_ids']):
608 for inv in r['invoice_ids']:
609 wf_service.trg_validate(uid, 'account.invoice', inv, 'invoice_cancel', cr)
610 sale_order_line_obj.write(cr, uid, [l.id for l in sale.order_line],
612 message = _("The sale order '%s' has been cancelled.") % (sale.name,)
613 self.log(cr, uid, sale.id, message)
614 self.write(cr, uid, ids, {'state': 'cancel'})
617 def action_wait(self, cr, uid, ids, *args):
618 for o in self.browse(cr, uid, ids):
619 if (o.order_policy == 'manual'):
620 self.write(cr, uid, [o.id], {'state': 'manual', 'date_confirm': time.strftime('%Y-%m-%d')})
622 self.write(cr, uid, [o.id], {'state': 'progress', 'date_confirm': time.strftime('%Y-%m-%d')})
623 self.pool.get('sale.order.line').button_confirm(cr, uid, [x.id for x in o.order_line])
624 message = _("The quotation '%s' has been converted to a sale order.") % (o.name,)
625 self.log(cr, uid, o.id, message)
628 def procurement_lines_get(self, cr, uid, ids, *args):
630 for order in self.browse(cr, uid, ids, context={}):
631 for line in order.order_line:
632 if line.procurement_id:
633 res.append(line.procurement_id.id)
636 # if mode == 'finished':
637 # returns True if all lines are done, False otherwise
638 # if mode == 'canceled':
639 # returns True if there is at least one canceled line, False otherwise
640 def test_state(self, cr, uid, ids, mode, *args):
641 assert mode in ('finished', 'canceled'), _("invalid mode for test_state")
646 write_cancel_ids = []
647 for order in self.browse(cr, uid, ids, context={}):
648 for line in order.order_line:
649 if (not line.procurement_id) or (line.procurement_id.state=='done'):
650 if line.state != 'done':
651 write_done_ids.append(line.id)
654 if line.procurement_id:
655 if (line.procurement_id.state == 'cancel'):
657 if line.state != 'exception':
658 write_cancel_ids.append(line.id)
662 self.pool.get('sale.order.line').write(cr, uid, write_done_ids, {'state': 'done'})
664 self.pool.get('sale.order.line').write(cr, uid, write_cancel_ids, {'state': 'exception'})
666 if mode == 'finished':
668 elif mode == 'canceled':
674 def action_ship_create(self, cr, uid, ids, *args):
675 wf_service = netsvc.LocalService("workflow")
677 company = self.pool.get('res.users').browse(cr, uid, uid).company_id
678 for order in self.browse(cr, uid, ids, context={}):
680 output_id = order.shop_id.warehouse_id.lot_output_id.id
682 for line in order.order_line:
684 date_planned = datetime.now() + relativedelta(days=line.delay or 0.0)
685 date_planned = (date_planned - timedelta(days=company.security_lead)).strftime('%Y-%m-%d %H:%M:%S')
687 if line.state == 'done':
690 if line.product_id and line.product_id.product_tmpl_id.type in ('product', 'consu'):
691 location_id = order.shop_id.warehouse_id.lot_stock_id.id
693 pick_name = self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.out')
694 picking_id = self.pool.get('stock.picking').create(cr, uid, {
696 'origin': order.name,
699 'move_type': order.picking_policy,
701 'address_id': order.partner_shipping_id.id,
703 'invoice_state': (order.order_policy=='picking' and '2binvoiced') or 'none',
704 'company_id': order.company_id.id,
706 move_id = self.pool.get('stock.move').create(cr, uid, {
707 'name': line.name[:64],
708 'picking_id': picking_id,
709 'product_id': line.product_id.id,
710 'date': date_planned,
711 'date_expected': date_planned,
712 'product_qty': line.product_uom_qty,
713 'product_uom': line.product_uom.id,
714 'product_uos_qty': line.product_uos_qty,
715 'product_uos': (line.product_uos and line.product_uos.id)\
716 or line.product_uom.id,
717 'product_packaging': line.product_packaging.id,
718 'address_id': line.address_allotment_id.id or order.partner_shipping_id.id,
719 'location_id': location_id,
720 'location_dest_id': output_id,
721 'sale_line_id': line.id,
722 'tracking_id': False,
726 'company_id': order.company_id.id,
729 proc_id = self.pool.get('procurement.order').create(cr, uid, {
731 'origin': order.name,
732 'date_planned': date_planned,
733 'product_id': line.product_id.id,
734 'product_qty': line.product_uom_qty,
735 'product_uom': line.product_uom.id,
736 'product_uos_qty': (line.product_uos and line.product_uos_qty)\
737 or line.product_uom_qty,
738 'product_uos': (line.product_uos and line.product_uos.id)\
739 or line.product_uom.id,
740 'location_id': order.shop_id.warehouse_id.lot_stock_id.id,
741 'procure_method': line.type,
743 'property_ids': [(6, 0, [x.id for x in line.property_ids])],
744 'company_id': order.company_id.id,
746 proc_ids.append(proc_id)
747 self.pool.get('sale.order.line').write(cr, uid, [line.id], {'procurement_id': proc_id})
750 for proc_id in proc_ids:
751 wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_confirm', cr)
754 wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
756 if order.state == 'shipping_except':
757 val['state'] = 'progress'
759 if (order.order_policy == 'manual'):
760 for line in order.order_line:
761 if (not line.invoiced) and (line.state not in ('cancel', 'draft')):
762 val['state'] = 'manual'
764 self.write(cr, uid, [order.id], val)
767 def action_ship_end(self, cr, uid, ids, context=None):
770 for order in self.browse(cr, uid, ids, context=context):
771 val = {'shipped': True}
772 if order.state == 'shipping_except':
773 val['state'] = 'progress'
774 if (order.order_policy == 'manual'):
775 for line in order.order_line:
776 if (not line.invoiced) and (line.state not in ('cancel', 'draft')):
777 val['state'] = 'manual'
779 for line in order.order_line:
781 if line.state == 'exception':
782 towrite.append(line.id)
784 self.pool.get('sale.order.line').write(cr, uid, towrite, {'state': 'done'}, context=context)
785 self.write(cr, uid, [order.id], val)
788 def _log_event(self, cr, uid, ids, factor=0.7, name='Open Order'):
789 invs = self.read(cr, uid, ids, ['date_order', 'partner_id', 'amount_untaxed'])
791 part = inv['partner_id'] and inv['partner_id'][0]
792 pr = inv['amount_untaxed'] or 0.0
793 partnertype = 'customer'
796 'name': 'Order: '+name,
798 'description': 'Order '+str(inv['id']),
801 'date': time.strftime('%Y-%m-%d'),
804 'partner_type': partnertype,
806 'planned_revenue': pr,
810 self.pool.get('res.partner.event').create(cr, uid, event)
812 def has_stockable_products(self, cr, uid, ids, *args):
813 for order in self.browse(cr, uid, ids):
814 for order_line in order.order_line:
815 if order_line.product_id and order_line.product_id.product_tmpl_id.type in ('product', 'consu'):
820 # TODO add a field price_unit_uos
821 # - update it on change product and unit price
822 # - use it in report if there is a uos
823 class sale_order_line(osv.osv):
825 def _amount_line(self, cr, uid, ids, field_name, arg, context=None):
826 tax_obj = self.pool.get('account.tax')
827 cur_obj = self.pool.get('res.currency')
829 context = context or {}
830 for line in self.browse(cr, uid, ids, context=context):
831 price = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
832 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)
833 cur = line.order_id.pricelist_id.currency_id
834 res[line.id] = cur_obj.round(cr, uid, cur, taxes['total'])
837 def _number_packages(self, cr, uid, ids, field_name, arg, context=None):
841 for line in self.browse(cr, uid, ids, context=context):
843 res[line.id] = int((line.product_uom_qty+line.product_packaging.qty-0.0001) / line.product_packaging.qty)
848 _name = 'sale.order.line'
849 _description = 'Sale Order Line'
851 'order_id': fields.many2one('sale.order', 'Order Reference', required=True, ondelete='cascade', select=True, readonly=True, states={'draft':[('readonly',False)]}),
852 'name': fields.char('Description', size=256, required=True, select=True, readonly=True, states={'draft': [('readonly', False)]}),
853 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of sale order lines."),
854 '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)]}),
855 'product_id': fields.many2one('product.product', 'Product', domain=[('sale_ok', '=', True)], change_default=True),
856 'invoice_lines': fields.many2many('account.invoice.line', 'sale_order_line_invoice_rel', 'order_line_id', 'invoice_id', 'Invoice Lines', readonly=True),
857 'invoiced': fields.boolean('Invoiced', readonly=True),
858 'procurement_id': fields.many2one('procurement.order', 'Procurement'),
859 'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Sale Price'), readonly=True, states={'draft': [('readonly', False)]}),
860 'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal', digits_compute= dp.get_precision('Sale Price')),
861 'tax_id': fields.many2many('account.tax', 'sale_order_tax', 'order_line_id', 'tax_id', 'Taxes', readonly=True, states={'draft': [('readonly', False)]}),
862 'type': fields.selection([('make_to_stock', 'from stock'), ('make_to_order', 'on order')], 'Procurement Method', required=True, readonly=True, states={'draft': [('readonly', False)]}),
863 'property_ids': fields.many2many('mrp.property', 'sale_order_line_property_rel', 'order_id', 'property_id', 'Properties', readonly=True, states={'draft': [('readonly', False)]}),
864 'address_allotment_id': fields.many2one('res.partner.address', 'Allotment Partner'),
865 'product_uom_qty': fields.float('Quantity (UoM)', digits=(16, 2), required=True, readonly=True, states={'draft': [('readonly', False)]}),
866 'product_uom': fields.many2one('product.uom', 'Unit of Measure ', required=True, readonly=True, states={'draft': [('readonly', False)]}),
867 'product_uos_qty': fields.float('Quantity (UoS)', readonly=True, states={'draft': [('readonly', False)]}),
868 'product_uos': fields.many2one('product.uom', 'Product UoS'),
869 'product_packaging': fields.many2one('product.packaging', 'Packaging'),
870 'move_ids': fields.one2many('stock.move', 'sale_line_id', 'Inventory Moves', readonly=True),
871 'discount': fields.float('Discount (%)', digits=(16, 2), readonly=True, states={'draft': [('readonly', False)]}),
872 'number_packages': fields.function(_number_packages, method=True, type='integer', string='Number Packages'),
873 'notes': fields.text('Notes'),
874 'th_weight': fields.float('Weight', readonly=True, states={'draft': [('readonly', False)]}),
875 'state': fields.selection([('draft', 'Draft'),('confirmed', 'Confirmed'),('done', 'Done'),('cancel', 'Cancelled'),('exception', 'Exception')], 'State', required=True, readonly=True,
876 help='* The \'Draft\' state is set when the related sale order in draft state. \
877 \n* The \'Confirmed\' state is set when the related sale order is confirmed. \
878 \n* The \'Exception\' state is set when the related sale order is set as exception. \
879 \n* The \'Done\' state is set when the sale order line has been picked. \
880 \n* The \'Cancelled\' state is set when a user cancel the sale order related.'),
881 'order_partner_id': fields.related('order_id', 'partner_id', type='many2one', relation='res.partner', string='Customer'),
882 'salesman_id':fields.related('order_id', 'user_id', type='many2one', relation='res.users', string='Salesman'),
883 'company_id': fields.related('order_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True, states={'draft': [('readonly', False)]}),
885 _order = 'sequence, id desc'
889 'product_uom_qty': 1,
890 'product_uos_qty': 1,
894 'type': 'make_to_stock',
895 'product_packaging': False
898 def invoice_line_create(self, cr, uid, ids, context=None):
902 def _get_line_qty(line):
903 if (line.order_id.invoice_quantity=='order') or not line.procurement_id:
905 return line.product_uos_qty or 0.0
906 return line.product_uom_qty
908 return self.pool.get('procurement.order').quantity_get(cr, uid,
909 line.procurement_id.id, context=context)
911 def _get_line_uom(line):
912 if (line.order_id.invoice_quantity=='order') or not line.procurement_id:
914 return line.product_uos.id
915 return line.product_uom.id
917 return self.pool.get('procurement.order').uom_get(cr, uid,
918 line.procurement_id.id, context=context)
922 for line in self.browse(cr, uid, ids, context=context):
923 if not line.invoiced:
925 a = line.product_id.product_tmpl_id.property_account_income.id
927 a = line.product_id.categ_id.property_account_income_categ.id
929 raise osv.except_osv(_('Error !'),
930 _('There is no income account defined ' \
931 'for this product: "%s" (id:%d)') % \
932 (line.product_id.name, line.product_id.id,))
934 prop = self.pool.get('ir.property').get(cr, uid,
935 'property_account_income_categ', 'product.category',
937 a = prop and prop.id or False
938 uosqty = _get_line_qty(line)
939 uos_id = _get_line_uom(line)
942 pu = round(line.price_unit * line.product_uom_qty / uosqty,
943 self.pool.get('decimal.precision').precision_get(cr, uid, 'Sale Price'))
944 fpos = line.order_id.fiscal_position or False
945 a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
947 raise osv.except_osv(_('Error !'),
948 _('There is no income category account defined in default Properties for Product Category or Fiscal Position is not defined !'))
949 inv_id = self.pool.get('account.invoice.line').create(cr, uid, {
951 'origin': line.order_id.name,
955 'discount': line.discount,
957 'product_id': line.product_id.id or False,
958 'invoice_line_tax_id': [(6, 0, [x.id for x in line.tax_id])],
960 'account_analytic_id': line.order_id.project_id and line.order_id.project_id.id or False,
962 cr.execute('insert into sale_order_line_invoice_rel (order_line_id,invoice_id) values (%s,%s)', (line.id, inv_id))
963 self.write(cr, uid, [line.id], {'invoiced': True})
964 sales[line.order_id.id] = True
965 create_ids.append(inv_id)
966 # Trigger workflow events
967 wf_service = netsvc.LocalService("workflow")
968 for sid in sales.keys():
969 wf_service.trg_write(uid, 'sale.order', sid, cr)
972 def button_cancel(self, cr, uid, ids, context=None):
975 for line in self.browse(cr, uid, ids, context=context):
977 raise osv.except_osv(_('Invalid action !'), _('You cannot cancel a sale order line that has already been invoiced !'))
978 for move_line in line.move_ids:
979 if move_line.state != 'cancel':
980 raise osv.except_osv(
981 _('Could not cancel sale order line!'),
982 _('You must first cancel stock moves attached to this sale order line.'))
983 return self.write(cr, uid, ids, {'state': 'cancel'})
985 def button_confirm(self, cr, uid, ids, context=None):
988 return self.write(cr, uid, ids, {'state': 'confirmed'})
990 def button_done(self, cr, uid, ids, context=None):
993 wf_service = netsvc.LocalService("workflow")
994 res = self.write(cr, uid, ids, {'state': 'done'})
995 for line in self.browse(cr, uid, ids, context=context):
996 wf_service.trg_write(uid, 'sale.order', line.order_id.id, cr)
999 def uos_change(self, cr, uid, ids, product_uos, product_uos_qty=0, product_id=None):
1000 product_obj = self.pool.get('product.product')
1002 return {'value': {'product_uom': product_uos,
1003 'product_uom_qty': product_uos_qty}, 'domain': {}}
1005 product = product_obj.browse(cr, uid, product_id)
1007 'product_uom': product.uom_id.id,
1009 # FIXME must depend on uos/uom of the product and not only of the coeff.
1012 'product_uom_qty': product_uos_qty / product.uos_coeff,
1013 'th_weight': product_uos_qty / product.uos_coeff * product.weight
1015 except ZeroDivisionError:
1017 return {'value': value}
1019 def copy_data(self, cr, uid, id, default=None, context=None):
1024 default.update({'state': 'draft', 'move_ids': [], 'invoiced': False, 'invoice_lines': []})
1025 return super(sale_order_line, self).copy_data(cr, uid, id, default, context=context)
1027 def product_id_change(self, cr, uid, ids, pricelist, product, qty=0,
1028 uom=False, qty_uos=0, uos=False, name='', partner_id=False,
1029 lang=False, update_tax=True, date_order=False, packaging=False, fiscal_position=False, flag=False):
1031 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.'))
1033 product_uom_obj = self.pool.get('product.uom')
1034 partner_obj = self.pool.get('res.partner')
1035 product_obj = self.pool.get('product.product')
1037 lang = partner_obj.browse(cr, uid, partner_id).lang
1038 context = {'lang': lang, 'partner_id': partner_id}
1041 return {'value': {'th_weight': 0, 'product_packaging': False,
1042 'product_uos_qty': qty}, 'domain': {'product_uom': [],
1045 date_order = time.strftime('%Y-%m-%d')
1048 product_obj = product_obj.browse(cr, uid, product, context=context)
1049 if not packaging and product_obj.packaging:
1050 packaging = product_obj.packaging[0].id
1051 result['product_packaging'] = packaging
1054 default_uom = product_obj.uom_id and product_obj.uom_id.id
1055 pack = self.pool.get('product.packaging').browse(cr, uid, packaging, context=context)
1056 q = product_uom_obj._compute_qty(cr, uid, uom, pack.qty, default_uom)
1057 # qty = qty - qty % q + q
1058 if qty and (q and not (qty % q) == 0):
1062 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)
1063 warn_msg = warn_msg + "\n\n" + _("EAN: ") + str(ean) + _(" Quantity: ") + str(qty_pack) + _(" Type of ul: ") + str(type_ul.name)
1065 'title': _('Picking Information !'),
1068 result['product_uom_qty'] = qty
1072 uom2 = product_uom_obj.browse(cr, uid, uom)
1073 if product_obj.uom_id.category_id.id != uom2.category_id.id:
1076 if product_obj.uos_id:
1077 uos2 = product_uom_obj.browse(cr, uid, uos)
1078 if product_obj.uos_id.category_id.id != uos2.category_id.id:
1082 if product_obj.description_sale:
1083 result['notes'] = product_obj.description_sale
1084 fpos = fiscal_position and self.pool.get('account.fiscal.position').browse(cr, uid, fiscal_position) or False
1085 if update_tax: #The quantity only have changed
1086 result['delay'] = (product_obj.sale_delay or 0.0)
1087 result['tax_id'] = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, product_obj.taxes_id)
1088 result.update({'type': product_obj.procure_method})
1091 result['name'] = self.pool.get('product.product').name_get(cr, uid, [product_obj.id], context=context)[0][1]
1093 if (not uom) and (not uos):
1094 result['product_uom'] = product_obj.uom_id.id
1095 if product_obj.uos_id:
1096 result['product_uos'] = product_obj.uos_id.id
1097 result['product_uos_qty'] = qty * product_obj.uos_coeff
1098 uos_category_id = product_obj.uos_id.category_id.id
1100 result['product_uos'] = False
1101 result['product_uos_qty'] = qty
1102 uos_category_id = False
1103 result['th_weight'] = qty * product_obj.weight
1104 domain = {'product_uom':
1105 [('category_id', '=', product_obj.uom_id.category_id.id)],
1107 [('category_id', '=', uos_category_id)]}
1109 elif uos and not uom: # only happens if uom is False
1110 result['product_uom'] = product_obj.uom_id and product_obj.uom_id.id
1111 result['product_uom_qty'] = qty_uos / product_obj.uos_coeff
1112 result['th_weight'] = result['product_uom_qty'] * product_obj.weight
1113 elif uom: # whether uos is set or not
1114 default_uom = product_obj.uom_id and product_obj.uom_id.id
1115 q = product_uom_obj._compute_qty(cr, uid, uom, qty, default_uom)
1116 if product_obj.uos_id:
1117 result['product_uos'] = product_obj.uos_id.id
1118 result['product_uos_qty'] = qty * product_obj.uos_coeff
1120 result['product_uos'] = False
1121 result['product_uos_qty'] = qty
1122 result['th_weight'] = q * product_obj.weight # Round the quantity up
1125 uom2 = product_obj.uom_id
1126 if (product_obj.type=='product') and (product_obj.virtual_available * uom2.factor < qty * product_obj.uom_id.factor) \
1127 and (product_obj.procure_method=='make_to_stock'):
1129 'title': _('Not enough stock !'),
1130 'message': _('You plan to sell %.2f %s but you only have %.2f %s available !\nThe real stock is %.2f %s. (without reservations)') %
1131 (qty, uom2 and uom2.name or product_obj.uom_id.name,
1132 max(0,product_obj.virtual_available), product_obj.uom_id.name,
1133 max(0,product_obj.qty_available), product_obj.uom_id.name)
1138 'title': 'No Pricelist !',
1140 'You have to select a pricelist or a customer in the sale form !\n'
1141 'Please set one before choosing a product.'
1144 price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist],
1145 product, qty or 1.0, partner_id, {
1151 'title': 'No valid pricelist line found !',
1153 "Couldn't find a pricelist line matching this product and quantity.\n"
1154 "You have to change either the product, the quantity or the pricelist."
1157 result.update({'price_unit': price})
1158 return {'value': result, 'domain': domain, 'warning': warning}
1160 def product_uom_change(self, cursor, user, ids, pricelist, product, qty=0,
1161 uom=False, qty_uos=0, uos=False, name='', partner_id=False,
1162 lang=False, update_tax=True, date_order=False):
1163 res = self.product_id_change(cursor, user, ids, pricelist, product,
1164 qty=qty, uom=uom, qty_uos=qty_uos, uos=uos, name=name,
1165 partner_id=partner_id, lang=lang, update_tax=update_tax,
1166 date_order=date_order)
1167 if 'product_uom' in res['value']:
1168 del res['value']['product_uom']
1170 res['value']['price_unit'] = 0.0
1173 def unlink(self, cr, uid, ids, context=None):
1176 """Allows to delete sale order lines in draft,cancel states"""
1177 for rec in self.browse(cr, uid, ids, context=context):
1178 if rec.state not in ['draft', 'cancel']:
1179 raise osv.except_osv(_('Invalid action !'), _('Cannot delete a sale order line which is %s !') %(rec.state,))
1180 return super(sale_order_line, self).unlink(cr, uid, ids, context=context)
1184 class sale_config_picking_policy(osv.osv_memory):
1185 _name = 'sale.config.picking_policy'
1186 _inherit = 'res.config'
1189 'name': fields.char('Name', size=64),
1190 'picking_policy': fields.selection([
1191 ('direct', 'Direct Delivery'),
1192 ('one', 'All at Once')
1193 ], 'Picking Default Policy', required=True, help="The Shipping Policy is used to configure per order if you want to deliver as soon as possible when one product is available or you wait that all products are available.."),
1194 'order_policy': fields.selection([
1195 ('manual', 'Invoice Based on Sales Orders'),
1196 ('picking', 'Invoice Based on Deliveries'),
1197 ], 'Shipping Default Policy', required=True,
1198 help="You can generate invoices based on sales orders or based on shippings."),
1199 'step': fields.selection([
1200 ('one', 'Delivery Order Only'),
1201 ('two', 'Picking List & Delivery Order')
1202 ], 'Steps To Deliver a Sale Order', required=True,
1203 help="By default, OpenERP is able to manage complex routing and paths "\
1204 "of products in your warehouse and partner locations. This will configure "\
1205 "the most common and simple methods to deliver products to the customer "\
1206 "in one or two operations by the worker.")
1209 'picking_policy': 'direct',
1210 'order_policy': 'manual',
1214 def execute(self, cr, uid, ids, context=None):
1217 for o in self.browse(cr, uid, ids, context=context):
1218 ir_values_obj = self.pool.get('ir.values')
1219 ir_values_obj.set(cr, uid, 'default', False, 'picking_policy', ['sale.order'], o.picking_policy)
1220 ir_values_obj.set(cr, uid, 'default', False, 'order_policy', ['sale.order'], o.order_policy)
1222 md = self.pool.get('ir.model.data')
1223 location_id = md._get_id(cr, uid, 'stock', 'stock_location_output')
1224 location_id = md.browse(cr, uid, location_id, context=context).res_id
1225 self.pool.get('stock.location').write(cr, uid, [location_id], {'chained_auto_packing': 'transparent'})
1227 sale_config_picking_policy()
1229 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: