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 = "Sales Shop"
35 'name': fields.char('Shop Name', size=64, required=True),
36 'payment_default_id': fields.many2one('account.payment.term', 'Default Payment Term', required=True),
37 'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse'),
38 'pricelist_id': fields.many2one('product.pricelist', 'Pricelist'),
39 'project_id': fields.many2one('account.analytic.account', 'Analytic Account', domain=[('parent_id', '!=', False)]),
40 'company_id': fields.many2one('res.company', 'Company', required=False),
43 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'sale.shop', context=c),
48 class sale_order(osv.osv):
50 _description = "Sales Order"
52 def copy(self, cr, uid, id, default=None, context=None):
60 'date_confirm': False,
61 'name': self.pool.get('ir.sequence').get(cr, uid, 'sale.order'),
63 return super(sale_order, self).copy(cr, uid, id, default, context=context)
65 def _amount_line_tax(self, cr, uid, line, context=None):
67 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']:
68 val += c.get('amount', 0.0)
71 def _amount_all(self, cr, uid, ids, field_name, arg, context=None):
72 cur_obj = self.pool.get('res.currency')
74 for order in self.browse(cr, uid, ids, context=context):
76 'amount_untaxed': 0.0,
81 cur = order.pricelist_id.currency_id
82 for line in order.order_line:
83 val1 += line.price_subtotal
84 val += self._amount_line_tax(cr, uid, line, context=context)
85 res[order.id]['amount_tax'] = cur_obj.round(cr, uid, cur, val)
86 res[order.id]['amount_untaxed'] = cur_obj.round(cr, uid, cur, val1)
87 res[order.id]['amount_total'] = res[order.id]['amount_untaxed'] + res[order.id]['amount_tax']
91 def _picked_rate(self, cr, uid, ids, name, arg, context=None):
98 p.sale_id, sum(m.product_qty), mp.state as mp_state
102 stock_picking p on (p.id=m.picking_id)
104 procurement_order mp on (mp.move_id=m.id)
106 p.sale_id IN %s GROUP BY mp.state, p.sale_id''', (tuple(ids),))
107 for oid, nbr, mp_state in cr.fetchall():
108 if mp_state == 'cancel':
110 if mp_state == 'done':
111 res[oid][0] += nbr or 0.0
112 res[oid][1] += nbr or 0.0
114 res[oid][1] += nbr or 0.0
119 res[r] = 100.0 * res[r][0] / res[r][1]
120 for order in self.browse(cr, uid, ids, context=context):
122 res[order.id] = 100.0
125 def _invoiced_rate(self, cursor, user, ids, name, arg, context=None):
127 for sale in self.browse(cursor, user, ids, context=context):
132 for invoice in sale.invoice_ids:
133 if invoice.state not in ('draft', 'cancel'):
134 tot += invoice.amount_untaxed
136 res[sale.id] = min(100.0, tot * 100.0 / (sale.amount_untaxed or 1.00))
141 def _invoiced(self, cursor, user, ids, name, arg, context=None):
143 for sale in self.browse(cursor, user, ids, context=context):
145 for invoice in sale.invoice_ids:
146 if invoice.state != 'paid':
149 if not sale.invoice_ids:
153 def _invoiced_search(self, cursor, user, obj, name, args, context=None):
162 clause += 'AND inv.state = \'paid\''
164 clause += 'AND inv.state != \'cancel\' AND sale.state != \'cancel\' AND inv.state <> \'paid\' AND rel.order_id = sale.id '
165 sale_clause = ', sale_order AS sale '
168 cursor.execute('SELECT rel.order_id ' \
169 'FROM sale_order_invoice_rel AS rel, account_invoice AS inv '+ sale_clause + \
170 'WHERE rel.invoice_id = inv.id ' + clause)
171 res = cursor.fetchall()
173 cursor.execute('SELECT sale.id ' \
174 'FROM sale_order AS sale ' \
175 'WHERE sale.id NOT IN ' \
176 '(SELECT rel.order_id ' \
177 'FROM sale_order_invoice_rel AS rel) and sale.state != \'cancel\'')
178 res.extend(cursor.fetchall())
180 return [('id', '=', 0)]
181 return [('id', 'in', [x[0] for x in res])]
183 def _get_order(self, cr, uid, ids, context=None):
185 for line in self.pool.get('sale.order.line').browse(cr, uid, ids, context=context):
186 result[line.order_id.id] = True
190 'name': fields.char('Order Reference', size=64, required=True,
191 readonly=True, states={'draft': [('readonly', False)]}, select=True),
192 'shop_id': fields.many2one('sale.shop', 'Shop', required=True, readonly=True, states={'draft': [('readonly', False)]}),
193 'origin': fields.char('Source Document', size=64, help="Reference of the document that generated this sales order request."),
194 'client_order_ref': fields.char('Customer Reference', size=64),
195 'state': fields.selection([
196 ('draft', 'Quotation'),
197 ('waiting_date', 'Waiting Schedule'),
198 ('manual', 'Manual In Progress'),
199 ('progress', 'In Progress'),
200 ('shipping_except', 'Shipping Exception'),
201 ('invoice_except', 'Invoice Exception'),
203 ('cancel', 'Cancelled')
204 ], 'Order State', readonly=True, help="Gives the state of the quotation or sales 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),
205 'date_order': fields.date('Ordered Date', required=True, readonly=True, select=True, states={'draft': [('readonly', False)]}),
206 'create_date': fields.date('Creation Date', readonly=True, select=True, help="Date on which sales order is created."),
207 'date_confirm': fields.date('Confirmation Date', readonly=True, select=True, help="Date on which sales order is confirmed."),
208 'user_id': fields.many2one('res.users', 'Salesman', states={'draft': [('readonly', False)]}, select=True),
209 'partner_id': fields.many2one('res.partner', 'Customer', readonly=True, states={'draft': [('readonly', False)]}, required=True, change_default=True, select=True),
210 'partner_invoice_id': fields.many2one('res.partner.address', 'Invoice Address', readonly=True, required=True, states={'draft': [('readonly', False)]}, help="Invoice address for current sales order."),
211 '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."),
212 'partner_shipping_id': fields.many2one('res.partner.address', 'Shipping Address', readonly=True, required=True, states={'draft': [('readonly', False)]}, help="Shipping address for current sales order."),
214 'incoterm': fields.many2one('stock.incoterms', 'Incoterm', help="Incoterm which stands for 'International Commercial terms' implies its a series of sales terms which are used in the commercial transaction."),
215 'picking_policy': fields.selection([('direct', 'Partial Delivery'), ('one', 'Complete Delivery')],
216 '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?"""),
217 'order_policy': fields.selection([
218 ('prepaid', 'Payment Before Delivery'),
219 ('manual', 'Shipping & Manual Invoice'),
220 ('postpaid', 'Invoice On Order After Delivery'),
221 ('picking', 'Invoice From The Picking'),
222 ], 'Shipping Policy', required=True, readonly=True, states={'draft': [('readonly', False)]},
223 help="""The Shipping Policy is used to synchronise invoice and delivery operations.
224 - The 'Pay Before delivery' choice will first generate the invoice and then generate the picking order after the payment of this invoice.
225 - 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.
226 - The 'Invoice On Order After Delivery' choice will generate the draft invoice based on sales order after all picking lists have been finished.
227 - The 'Invoice From The Picking' choice is used to create an invoice during the picking process."""),
228 'pricelist_id': fields.many2one('product.pricelist', 'Pricelist', required=True, readonly=True, states={'draft': [('readonly', False)]}, help="Pricelist for current sales order."),
229 'project_id': fields.many2one('account.analytic.account', 'Analytic Account', readonly=True, states={'draft': [('readonly', False)]}, help="The analytic account related to a sales order."),
231 'order_line': fields.one2many('sale.order.line', 'order_id', 'Order Lines', readonly=True, states={'draft': [('readonly', False)]}),
232 'invoice_ids': fields.many2many('account.invoice', 'sale_order_invoice_rel', 'order_id', 'invoice_id', 'Invoices', readonly=True, help="This is the list of invoices that have been generated for this sales order. The same sales order may have been invoiced in several times (by line for example)."),
233 'picking_ids': fields.one2many('stock.picking', 'sale_id', 'Related Picking', readonly=True, help="This is a list of picking that has been generated for this sales order."),
234 'shipped': fields.boolean('Delivered', readonly=True, help="It indicates that the sales order has been delivered. This field is updated only after the scheduler(s) have been launched."),
235 'picked_rate': fields.function(_picked_rate, method=True, string='Picked', type='float'),
236 'invoiced_rate': fields.function(_invoiced_rate, method=True, string='Invoiced', type='float'),
237 'invoiced': fields.function(_invoiced, method=True, string='Paid',
238 fnct_search=_invoiced_search, type='boolean', help="It indicates that an invoice has been paid."),
239 'note': fields.text('Notes'),
241 'amount_untaxed': fields.function(_amount_all, method=True, digits_compute= dp.get_precision('Sale Price'), string='Untaxed Amount',
243 'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line'], 10),
244 'sale.order.line': (_get_order, ['price_unit', 'tax_id', 'discount', 'product_uom_qty'], 10),
246 multi='sums', help="The amount without tax."),
247 'amount_tax': fields.function(_amount_all, method=True, digits_compute= dp.get_precision('Sale Price'), string='Taxes',
249 'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line'], 10),
250 'sale.order.line': (_get_order, ['price_unit', 'tax_id', 'discount', 'product_uom_qty'], 10),
252 multi='sums', help="The tax amount."),
253 'amount_total': fields.function(_amount_all, method=True, digits_compute= dp.get_precision('Sale Price'), string='Total',
255 'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line'], 10),
256 'sale.order.line': (_get_order, ['price_unit', 'tax_id', 'discount', 'product_uom_qty'], 10),
258 multi='sums', help="The total amount."),
260 '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, readonly=True, states={'draft': [('readonly', False)]}),
261 'payment_term': fields.many2one('account.payment.term', 'Payment Term'),
262 'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position'),
263 'company_id': fields.related('shop_id','company_id',type='many2one',relation='res.company',string='Company',store=True,readonly=True)
266 'picking_policy': 'direct',
267 'date_order': lambda *a: time.strftime('%Y-%m-%d'),
268 'order_policy': 'manual',
270 'user_id': lambda obj, cr, uid, context: uid,
271 'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'sale.order'),
272 'invoice_quantity': 'order',
273 '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'],
274 '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'],
275 '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'],
278 ('name_uniq', 'unique(name)', 'Order Reference must be unique !'),
283 def unlink(self, cr, uid, ids, context=None):
284 sale_orders = self.read(cr, uid, ids, ['state'], context=context)
286 for s in sale_orders:
287 if s['state'] in ['draft', 'cancel']:
288 unlink_ids.append(s['id'])
290 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Sales Order(s) which are already confirmed !'))
291 return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
293 def onchange_shop_id(self, cr, uid, ids, shop_id):
296 shop = self.pool.get('sale.shop').browse(cr, uid, shop_id)
297 v['project_id'] = shop.project_id.id
298 # Que faire si le client a une pricelist a lui ?
299 if shop.pricelist_id.id:
300 v['pricelist_id'] = shop.pricelist_id.id
303 def action_cancel_draft(self, cr, uid, ids, *args):
306 cr.execute('select id from sale_order_line where order_id IN %s and state=%s', (tuple(ids), 'cancel'))
307 line_ids = map(lambda x: x[0], cr.fetchall())
308 self.write(cr, uid, ids, {'state': 'draft', 'invoice_ids': [], 'shipped': 0})
309 self.pool.get('sale.order.line').write(cr, uid, line_ids, {'invoiced': False, 'state': 'draft', 'invoice_lines': [(6, 0, [])]})
310 wf_service = netsvc.LocalService("workflow")
312 # Deleting the existing instance of workflow for SO
313 wf_service.trg_delete(uid, 'sale.order', inv_id, cr)
314 wf_service.trg_create(uid, 'sale.order', inv_id, cr)
315 for (id,name) in self.name_get(cr, uid, ids):
316 message = _("The sales order '%s' has been set in draft state.") %(name,)
317 self.log(cr, uid, id, message)
320 def onchange_partner_id(self, cr, uid, ids, part):
322 return {'value': {'partner_invoice_id': False, 'partner_shipping_id': False, 'partner_order_id': False, 'payment_term': False, 'fiscal_position': False}}
324 addr = self.pool.get('res.partner').address_get(cr, uid, [part], ['delivery', 'invoice', 'contact'])
325 part = self.pool.get('res.partner').browse(cr, uid, part)
326 pricelist = part.property_product_pricelist and part.property_product_pricelist.id or False
327 payment_term = part.property_payment_term and part.property_payment_term.id or False
328 fiscal_position = part.property_account_position and part.property_account_position.id or False
329 dedicated_salesman = part.user_id and part.user_id.id or uid
331 'partner_invoice_id': addr['invoice'],
332 'partner_order_id': addr['contact'],
333 'partner_shipping_id': addr['delivery'],
334 'payment_term': payment_term,
335 'fiscal_position': fiscal_position,
336 'user_id': dedicated_salesman,
339 val['pricelist_id'] = pricelist
340 return {'value': val}
342 def shipping_policy_change(self, cr, uid, ids, policy, context=None):
346 if policy == 'prepaid':
348 elif policy == 'picking':
349 inv_qty = 'procurement'
350 return {'value': {'invoice_quantity': inv_qty}}
352 def write(self, cr, uid, ids, vals, context=None):
353 if vals.get('order_policy', False):
354 if vals['order_policy'] == 'prepaid':
355 vals.update({'invoice_quantity': 'order'})
356 elif vals['order_policy'] == 'picking':
357 vals.update({'invoice_quantity': 'procurement'})
358 return super(sale_order, self).write(cr, uid, ids, vals, context=context)
360 def create(self, cr, uid, vals, context=None):
361 if vals.get('order_policy', False):
362 if vals['order_policy'] == 'prepaid':
363 vals.update({'invoice_quantity': 'order'})
364 if vals['order_policy'] == 'picking':
365 vals.update({'invoice_quantity': 'procurement'})
366 return super(sale_order, self).create(cr, uid, vals, context=context)
368 def button_dummy(self, cr, uid, ids, context=None):
371 #FIXME: the method should return the list of invoices created (invoice_ids)
372 # and not the id of the last invoice created (res). The problem is that we
373 # cannot change it directly since the method is called by the sales order
374 # workflow and I suppose it expects a single id...
375 def _inv_get(self, cr, uid, order, context=None):
378 def _make_invoice(self, cr, uid, order, lines, context=None):
379 journal_obj = self.pool.get('account.journal')
380 inv_obj = self.pool.get('account.invoice')
381 obj_invoice_line = self.pool.get('account.invoice.line')
385 journal_ids = journal_obj.search(cr, uid, [('type', '=', 'sale'), ('company_id', '=', order.company_id.id)], limit=1)
387 raise osv.except_osv(_('Error !'),
388 _('There is no sales journal defined for this company: "%s" (id:%d)') % (order.company_id.name, order.company_id.id))
389 a = order.partner_id.property_account_receivable.id
390 pay_term = order.payment_term and order.payment_term.id or False
391 invoiced_sale_line_ids = self.pool.get('sale.order.line').search(cr, uid, [('order_id', '=', order.id), ('invoiced', '=', True)], context=context)
392 from_line_invoice_ids = []
393 for invoiced_sale_line_id in self.pool.get('sale.order.line').browse(cr, uid, invoiced_sale_line_ids, context=context):
394 for invoice_line_id in invoiced_sale_line_id.invoice_lines:
395 if invoice_line_id.invoice_id.id not in from_line_invoice_ids:
396 from_line_invoice_ids.append(invoice_line_id.invoice_id.id)
397 for preinv in order.invoice_ids:
398 if preinv.state not in ('cancel',) and preinv.id not in from_line_invoice_ids:
399 for preline in preinv.invoice_line:
400 inv_line_id = obj_invoice_line.copy(cr, uid, preline.id, {'invoice_id': False, 'price_unit': -preline.price_unit})
401 lines.append(inv_line_id)
403 'name': order.client_order_ref or '',
404 'origin': order.name,
405 'type': 'out_invoice',
406 'reference': order.client_order_ref or order.name,
408 'partner_id': order.partner_id.id,
409 'journal_id': journal_ids[0],
410 'address_invoice_id': order.partner_invoice_id.id,
411 'address_contact_id': order.partner_order_id.id,
412 'invoice_line': [(6, 0, lines)],
413 'currency_id': order.pricelist_id.currency_id.id,
414 'comment': order.note,
415 'payment_term': pay_term,
416 'fiscal_position': order.fiscal_position.id or order.partner_id.property_account_position.id,
417 'date_invoice': context.get('date_invoice',False),
418 'company_id': order.company_id.id,
419 'user_id': order.user_id and order.user_id.id or False
421 inv.update(self._inv_get(cr, uid, order))
422 inv_id = inv_obj.create(cr, uid, inv, context=context)
423 data = inv_obj.onchange_payment_term_date_invoice(cr, uid, [inv_id], pay_term, time.strftime('%Y-%m-%d'))
424 if data.get('value', False):
425 inv_obj.write(cr, uid, [inv_id], data['value'], context=context)
426 inv_obj.button_compute(cr, uid, [inv_id])
429 def manual_invoice(self, cr, uid, ids, context=None):
430 mod_obj = self.pool.get('ir.model.data')
431 wf_service = netsvc.LocalService("workflow")
435 for record in self.pool.get('sale.order').browse(cr, uid, id).invoice_ids:
436 inv_ids.add(record.id)
437 # inv_ids would have old invoices if any
439 wf_service.trg_validate(uid, 'sale.order', id, 'manual_invoice', cr)
440 for record in self.pool.get('sale.order').browse(cr, uid, id).invoice_ids:
441 inv_ids1.add(record.id)
442 inv_ids = list(inv_ids1.difference(inv_ids))
444 res = mod_obj.get_object_reference(cr, uid, 'account', 'invoice_form')
445 res_id = res and res[1] or False,
448 'name': 'Customer Invoices',
452 'res_model': 'account.invoice',
453 'context': "{'type':'out_invoice'}",
454 'type': 'ir.actions.act_window',
457 'res_id': inv_ids and inv_ids[0] or False,
460 def action_invoice_create(self, cr, uid, ids, grouped=False, states=['confirmed', 'done', 'exception'], date_inv = False, context=None):
464 picking_obj = self.pool.get('stock.picking')
465 invoice = self.pool.get('account.invoice')
466 obj_sale_order_line = self.pool.get('sale.order.line')
469 # If date was specified, use it as date invoiced, usefull when invoices are generated this month and put the
470 # last day of the last month as invoice date
472 context['date_inv'] = date_inv
473 for o in self.browse(cr, uid, ids, context=context):
475 for line in o.order_line:
478 elif (line.state in states):
479 lines.append(line.id)
480 created_lines = obj_sale_order_line.invoice_line_create(cr, uid, lines)
482 invoices.setdefault(o.partner_id.id, []).append((o, created_lines))
484 for o in self.browse(cr, uid, ids, context=context):
485 for i in o.invoice_ids:
486 if i.state == 'draft':
488 for val in invoices.values():
490 res = self._make_invoice(cr, uid, val[0][0], reduce(lambda x, y: x + y, [l for o, l in val], []), context=context)
493 invoice_ref += o.name + '|'
494 self.write(cr, uid, [o.id], {'state': 'progress'})
495 if o.order_policy == 'picking':
496 picking_obj.write(cr, uid, map(lambda x: x.id, o.picking_ids), {'invoice_state': 'invoiced'})
497 cr.execute('insert into sale_order_invoice_rel (order_id,invoice_id) values (%s,%s)', (o.id, res))
498 invoice.write(cr, uid, [res], {'origin': invoice_ref, 'name': invoice_ref})
500 for order, il in val:
501 res = self._make_invoice(cr, uid, order, il, context=context)
502 invoice_ids.append(res)
503 self.write(cr, uid, [order.id], {'state': 'progress'})
504 if order.order_policy == 'picking':
505 picking_obj.write(cr, uid, map(lambda x: x.id, order.picking_ids), {'invoice_state': 'invoiced'})
506 cr.execute('insert into sale_order_invoice_rel (order_id,invoice_id) values (%s,%s)', (order.id, res))
509 def action_invoice_cancel(self, cr, uid, ids, context=None):
512 for sale in self.browse(cr, uid, ids, context=context):
513 for line in sale.order_line:
515 # Check if the line is invoiced (has asociated invoice
516 # lines from non-cancelled invoices).
519 for iline in line.invoice_lines:
520 if iline.invoice_id and iline.invoice_id.state != 'cancel':
523 # Update the line (only when needed)
524 if line.invoiced != invoiced:
525 self.pool.get('sale.order.line').write(cr, uid, [line.id], {'invoiced': invoiced}, context=context)
526 self.write(cr, uid, ids, {'state': 'invoice_except', 'invoice_ids': False}, context=context)
529 def action_invoice_end(self, cr, uid, ids, context=None):
530 for order in self.browse(cr, uid, ids, context=context):
532 # Update the sale order lines state (and invoiced flag).
534 for line in order.order_line:
537 # Check if the line is invoiced (has asociated invoice
538 # lines from non-cancelled invoices).
541 for iline in line.invoice_lines:
542 if iline.invoice_id and iline.invoice_id.state != 'cancel':
545 if line.invoiced != invoiced:
546 vals['invoiced'] = invoiced
547 # If the line was in exception state, now it gets confirmed.
548 if line.state == 'exception':
549 vals['state'] = 'confirmed'
550 # Update the line (only when needed).
552 self.pool.get('sale.order.line').write(cr, uid, [line.id], vals, context=context)
554 # Update the sales order state.
556 if order.state == 'invoice_except':
557 self.write(cr, uid, [order.id], {'state': 'progress'}, context=context)
560 def action_cancel(self, cr, uid, ids, context=None):
561 wf_service = netsvc.LocalService("workflow")
564 sale_order_line_obj = self.pool.get('sale.order.line')
565 proc_obj = self.pool.get('procurement.order')
566 for sale in self.browse(cr, uid, ids, context=context):
567 for pick in sale.picking_ids:
568 if pick.state not in ('draft', 'cancel'):
569 raise osv.except_osv(
570 _('Could not cancel sales order !'),
571 _('You must first cancel all picking attached to this sales order.'))
572 if pick.state == 'cancel':
573 for mov in pick.move_lines:
574 proc_ids = proc_obj.search(cr, uid, [('move_id', '=', mov.id)])
576 for proc in proc_ids:
577 wf_service.trg_validate(uid, 'procurement.order', proc, 'button_check', cr)
578 for r in self.read(cr, uid, ids, ['picking_ids']):
579 for pick in r['picking_ids']:
580 wf_service.trg_validate(uid, 'stock.picking', pick, 'button_cancel', cr)
581 for inv in sale.invoice_ids:
582 if inv.state not in ('draft', 'cancel'):
583 raise osv.except_osv(
584 _('Could not cancel this sales order !'),
585 _('You must first cancel all invoices attached to this sales order.'))
586 for r in self.read(cr, uid, ids, ['invoice_ids']):
587 for inv in r['invoice_ids']:
588 wf_service.trg_validate(uid, 'account.invoice', inv, 'invoice_cancel', cr)
589 sale_order_line_obj.write(cr, uid, [l.id for l in sale.order_line],
591 message = _("The sales order '%s' has been cancelled.") % (sale.name,)
592 self.log(cr, uid, sale.id, message)
593 self.write(cr, uid, ids, {'state': 'cancel'})
596 def action_wait(self, cr, uid, ids, *args):
597 for o in self.browse(cr, uid, ids):
598 if (o.order_policy == 'manual'):
599 self.write(cr, uid, [o.id], {'state': 'manual', 'date_confirm': time.strftime('%Y-%m-%d')})
601 self.write(cr, uid, [o.id], {'state': 'progress', 'date_confirm': time.strftime('%Y-%m-%d')})
602 self.pool.get('sale.order.line').button_confirm(cr, uid, [x.id for x in o.order_line])
603 message = _("The quotation '%s' has been converted to a sales order.") % (o.name,)
604 self.log(cr, uid, o.id, message)
607 def procurement_lines_get(self, cr, uid, ids, *args):
609 for order in self.browse(cr, uid, ids, context={}):
610 for line in order.order_line:
611 if line.procurement_id:
612 res.append(line.procurement_id.id)
615 # if mode == 'finished':
616 # returns True if all lines are done, False otherwise
617 # if mode == 'canceled':
618 # returns True if there is at least one canceled line, False otherwise
619 def test_state(self, cr, uid, ids, mode, *args):
620 assert mode in ('finished', 'canceled'), _("invalid mode for test_state")
625 write_cancel_ids = []
626 for order in self.browse(cr, uid, ids, context={}):
627 for line in order.order_line:
628 if (not line.procurement_id) or (line.procurement_id.state=='done'):
629 if line.state != 'done':
630 write_done_ids.append(line.id)
633 if line.procurement_id:
634 if (line.procurement_id.state == 'cancel'):
636 if line.state != 'exception':
637 write_cancel_ids.append(line.id)
641 self.pool.get('sale.order.line').write(cr, uid, write_done_ids, {'state': 'done'})
643 self.pool.get('sale.order.line').write(cr, uid, write_cancel_ids, {'state': 'exception'})
645 if mode == 'finished':
647 elif mode == 'canceled':
653 def action_ship_create(self, cr, uid, ids, *args):
654 wf_service = netsvc.LocalService("workflow")
656 move_obj = self.pool.get('stock.move')
657 proc_obj = self.pool.get('procurement.order')
658 company = self.pool.get('res.users').browse(cr, uid, uid).company_id
659 for order in self.browse(cr, uid, ids, context={}):
661 output_id = order.shop_id.warehouse_id.lot_output_id.id
663 for line in order.order_line:
665 date_planned = datetime.now() + relativedelta(days=line.delay or 0.0)
666 date_planned = (date_planned - timedelta(days=company.security_lead)).strftime('%Y-%m-%d %H:%M:%S')
668 if line.state == 'done':
671 if line.product_id and line.product_id.product_tmpl_id.type in ('product', 'consu'):
672 location_id = order.shop_id.warehouse_id.lot_stock_id.id
674 pick_name = self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.out')
675 picking_id = self.pool.get('stock.picking').create(cr, uid, {
677 'origin': order.name,
680 'move_type': order.picking_policy,
682 'address_id': order.partner_shipping_id.id,
684 'invoice_state': (order.order_policy=='picking' and '2binvoiced') or 'none',
685 'company_id': order.company_id.id,
687 move_id = self.pool.get('stock.move').create(cr, uid, {
688 'name': line.name[:64],
689 'picking_id': picking_id,
690 'product_id': line.product_id.id,
691 'date': date_planned,
692 'date_expected': date_planned,
693 'product_qty': line.product_uom_qty,
694 'product_uom': line.product_uom.id,
695 'product_uos_qty': line.product_uos_qty,
696 'product_uos': (line.product_uos and line.product_uos.id)\
697 or line.product_uom.id,
698 'product_packaging': line.product_packaging.id,
699 'address_id': line.address_allotment_id.id or order.partner_shipping_id.id,
700 'location_id': location_id,
701 'location_dest_id': output_id,
702 'sale_line_id': line.id,
703 'tracking_id': False,
707 'company_id': order.company_id.id,
711 proc_id = self.pool.get('procurement.order').create(cr, uid, {
713 'origin': order.name,
714 'date_planned': date_planned,
715 'product_id': line.product_id.id,
716 'product_qty': line.product_uom_qty,
717 'product_uom': line.product_uom.id,
718 'product_uos_qty': (line.product_uos and line.product_uos_qty)\
719 or line.product_uom_qty,
720 'product_uos': (line.product_uos and line.product_uos.id)\
721 or line.product_uom.id,
722 'location_id': order.shop_id.warehouse_id.lot_stock_id.id,
723 'procure_method': line.type,
725 'property_ids': [(6, 0, [x.id for x in line.property_ids])],
726 'company_id': order.company_id.id,
728 proc_ids.append(proc_id)
729 self.pool.get('sale.order.line').write(cr, uid, [line.id], {'procurement_id': proc_id})
730 if order.state == 'shipping_except':
731 for pick in order.picking_ids:
732 for move in pick.move_lines:
733 if move.state == 'cancel':
734 mov_ids = move_obj.search(cr, uid, [('state', '=', 'cancel'),('sale_line_id', '=', line.id),('picking_id', '=', pick.id)])
736 for mov in move_obj.browse(cr, uid, mov_ids):
737 move_obj.write(cr, uid, [move_id], {'product_qty': mov.product_qty, 'product_uos_qty': mov.product_uos_qty})
738 proc_obj.write(cr, uid, [proc_id], {'product_qty': mov.product_qty, 'product_uos_qty': mov.product_uos_qty})
743 wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
745 for proc_id in proc_ids:
746 wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_confirm', cr)
748 if order.state == 'shipping_except':
749 val['state'] = 'progress'
750 val['shipped'] = False
752 if (order.order_policy == 'manual'):
753 for line in order.order_line:
754 if (not line.invoiced) and (line.state not in ('cancel', 'draft')):
755 val['state'] = 'manual'
757 self.write(cr, uid, [order.id], val)
760 def action_ship_end(self, cr, uid, ids, context=None):
761 for order in self.browse(cr, uid, ids, context=context):
762 val = {'shipped': True}
763 if order.state == 'shipping_except':
764 val['state'] = 'progress'
765 if (order.order_policy == 'manual'):
766 for line in order.order_line:
767 if (not line.invoiced) and (line.state not in ('cancel', 'draft')):
768 val['state'] = 'manual'
770 for line in order.order_line:
772 if line.state == 'exception':
773 towrite.append(line.id)
775 self.pool.get('sale.order.line').write(cr, uid, towrite, {'state': 'done'}, context=context)
776 self.write(cr, uid, [order.id], val)
779 def _log_event(self, cr, uid, ids, factor=0.7, name='Open Order'):
780 invs = self.read(cr, uid, ids, ['date_order', 'partner_id', 'amount_untaxed'])
782 part = inv['partner_id'] and inv['partner_id'][0]
783 pr = inv['amount_untaxed'] or 0.0
784 partnertype = 'customer'
787 'name': 'Order: '+name,
789 'description': 'Order '+str(inv['id']),
792 'date': time.strftime('%Y-%m-%d'),
795 'partner_type': partnertype,
797 'planned_revenue': pr,
801 self.pool.get('res.partner.event').create(cr, uid, event)
803 def has_stockable_products(self, cr, uid, ids, *args):
804 for order in self.browse(cr, uid, ids):
805 for order_line in order.order_line:
806 if order_line.product_id and order_line.product_id.product_tmpl_id.type in ('product', 'consu'):
811 # TODO add a field price_unit_uos
812 # - update it on change product and unit price
813 # - use it in report if there is a uos
814 class sale_order_line(osv.osv):
816 def _amount_line(self, cr, uid, ids, field_name, arg, context=None):
817 tax_obj = self.pool.get('account.tax')
818 cur_obj = self.pool.get('res.currency')
822 for line in self.browse(cr, uid, ids, context=context):
823 price = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
824 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)
825 cur = line.order_id.pricelist_id.currency_id
826 res[line.id] = cur_obj.round(cr, uid, cur, taxes['total'])
829 def _number_packages(self, cr, uid, ids, field_name, arg, context=None):
831 for line in self.browse(cr, uid, ids, context=context):
833 res[line.id] = int((line.product_uom_qty+line.product_packaging.qty-0.0001) / line.product_packaging.qty)
838 _name = 'sale.order.line'
839 _description = 'Sales Order Line'
841 'order_id': fields.many2one('sale.order', 'Order Reference', required=True, ondelete='cascade', select=True, readonly=True, states={'draft':[('readonly',False)]}),
842 'name': fields.char('Description', size=256, required=True, select=True, readonly=True, states={'draft': [('readonly', False)]}),
843 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of sales order lines."),
844 'delay': fields.float('Delivery Lead Time', required=True, help="Number of days between the order confirmation the shipping of the products to the customer", readonly=True, states={'draft': [('readonly', False)]}),
845 'product_id': fields.many2one('product.product', 'Product', domain=[('sale_ok', '=', True)], change_default=True),
846 'invoice_lines': fields.many2many('account.invoice.line', 'sale_order_line_invoice_rel', 'order_line_id', 'invoice_id', 'Invoice Lines', readonly=True),
847 'invoiced': fields.boolean('Invoiced', readonly=True),
848 'procurement_id': fields.many2one('procurement.order', 'Procurement'),
849 'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Sale Price'), readonly=True, states={'draft': [('readonly', False)]}),
850 'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal', digits_compute= dp.get_precision('Sale Price')),
851 'tax_id': fields.many2many('account.tax', 'sale_order_tax', 'order_line_id', 'tax_id', 'Taxes', readonly=True, states={'draft': [('readonly', False)]}),
852 'type': fields.selection([('make_to_stock', 'from stock'), ('make_to_order', 'on order')], 'Procurement Method', required=True, readonly=True, states={'draft': [('readonly', False)]}),
853 'property_ids': fields.many2many('mrp.property', 'sale_order_line_property_rel', 'order_id', 'property_id', 'Properties', readonly=True, states={'draft': [('readonly', False)]}),
854 'address_allotment_id': fields.many2one('res.partner.address', 'Allotment Partner'),
855 'product_uom_qty': fields.float('Quantity (UoM)', digits=(16, 2), required=True, readonly=True, states={'draft': [('readonly', False)]}),
856 'product_uom': fields.many2one('product.uom', 'Unit of Measure ', required=True, readonly=True, states={'draft': [('readonly', False)]}),
857 'product_uos_qty': fields.float('Quantity (UoS)', readonly=True, states={'draft': [('readonly', False)]}),
858 'product_uos': fields.many2one('product.uom', 'Product UoS'),
859 'product_packaging': fields.many2one('product.packaging', 'Packaging'),
860 'move_ids': fields.one2many('stock.move', 'sale_line_id', 'Inventory Moves', readonly=True),
861 'discount': fields.float('Discount (%)', digits=(16, 2), readonly=True, states={'draft': [('readonly', False)]}),
862 'number_packages': fields.function(_number_packages, method=True, type='integer', string='Number Packages'),
863 'notes': fields.text('Notes'),
864 'th_weight': fields.float('Weight', readonly=True, states={'draft': [('readonly', False)]}),
865 'state': fields.selection([('draft', 'Draft'),('confirmed', 'Confirmed'),('done', 'Done'),('cancel', 'Cancelled'),('exception', 'Exception')], 'State', required=True, readonly=True,
866 help='* The \'Draft\' state is set when the related sales order in draft state. \
867 \n* The \'Confirmed\' state is set when the related sales order is confirmed. \
868 \n* The \'Exception\' state is set when the related sales order is set as exception. \
869 \n* The \'Done\' state is set when the sales order line has been picked. \
870 \n* The \'Cancelled\' state is set when a user cancel the sales order related.'),
871 'order_partner_id': fields.related('order_id', 'partner_id', type='many2one', relation='res.partner', store=True, string='Customer'),
872 'salesman_id':fields.related('order_id', 'user_id', type='many2one', relation='res.users', store=True, string='Salesman'),
873 'company_id': fields.related('order_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True, states={'draft': [('readonly', False)]}),
875 _order = 'sequence, id desc'
879 'product_uom_qty': 1,
880 'product_uos_qty': 1,
884 'type': 'make_to_stock',
885 'product_packaging': False,
889 def invoice_line_create(self, cr, uid, ids, context=None):
893 def _get_line_qty(line):
894 if (line.order_id.invoice_quantity=='order') or not line.procurement_id:
896 return line.product_uos_qty or 0.0
897 return line.product_uom_qty
899 return self.pool.get('procurement.order').quantity_get(cr, uid,
900 line.procurement_id.id, context=context)
902 def _get_line_uom(line):
903 if (line.order_id.invoice_quantity=='order') or not line.procurement_id:
905 return line.product_uos.id
906 return line.product_uom.id
908 return self.pool.get('procurement.order').uom_get(cr, uid,
909 line.procurement_id.id, context=context)
913 for line in self.browse(cr, uid, ids, context=context):
914 if not line.invoiced:
916 a = line.product_id.product_tmpl_id.property_account_income.id
918 a = line.product_id.categ_id.property_account_income_categ.id
920 raise osv.except_osv(_('Error !'),
921 _('There is no income account defined ' \
922 'for this product: "%s" (id:%d)') % \
923 (line.product_id.name, line.product_id.id,))
925 prop = self.pool.get('ir.property').get(cr, uid,
926 'property_account_income_categ', 'product.category',
928 a = prop and prop.id or False
929 uosqty = _get_line_qty(line)
930 uos_id = _get_line_uom(line)
933 pu = round(line.price_unit * line.product_uom_qty / uosqty,
934 self.pool.get('decimal.precision').precision_get(cr, uid, 'Sale Price'))
935 fpos = line.order_id.fiscal_position or False
936 a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
938 raise osv.except_osv(_('Error !'),
939 _('There is no income category account defined in default Properties for Product Category or Fiscal Position is not defined !'))
940 inv_id = self.pool.get('account.invoice.line').create(cr, uid, {
942 'origin': line.order_id.name,
946 'discount': line.discount,
948 'product_id': line.product_id.id or False,
949 'invoice_line_tax_id': [(6, 0, [x.id for x in line.tax_id])],
951 'account_analytic_id': line.order_id.project_id and line.order_id.project_id.id or False,
953 cr.execute('insert into sale_order_line_invoice_rel (order_line_id,invoice_id) values (%s,%s)', (line.id, inv_id))
954 self.write(cr, uid, [line.id], {'invoiced': True})
955 sales[line.order_id.id] = True
956 create_ids.append(inv_id)
957 # Trigger workflow events
958 wf_service = netsvc.LocalService("workflow")
959 for sid in sales.keys():
960 wf_service.trg_write(uid, 'sale.order', sid, cr)
963 def button_cancel(self, cr, uid, ids, context=None):
964 for line in self.browse(cr, uid, ids, context=context):
966 raise osv.except_osv(_('Invalid action !'), _('You cannot cancel a sales order line that has already been invoiced !'))
967 for move_line in line.move_ids:
968 if move_line.state != 'cancel':
969 raise osv.except_osv(
970 _('Could not cancel sales order line!'),
971 _('You must first cancel stock moves attached to this sales order line.'))
972 return self.write(cr, uid, ids, {'state': 'cancel'})
974 def button_confirm(self, cr, uid, ids, context=None):
975 return self.write(cr, uid, ids, {'state': 'confirmed'})
977 def button_done(self, cr, uid, ids, context=None):
978 wf_service = netsvc.LocalService("workflow")
979 res = self.write(cr, uid, ids, {'state': 'done'})
980 for line in self.browse(cr, uid, ids, context=context):
981 wf_service.trg_write(uid, 'sale.order', line.order_id.id, cr)
984 def uos_change(self, cr, uid, ids, product_uos, product_uos_qty=0, product_id=None):
985 product_obj = self.pool.get('product.product')
987 return {'value': {'product_uom': product_uos,
988 'product_uom_qty': product_uos_qty}, 'domain': {}}
990 product = product_obj.browse(cr, uid, product_id)
992 'product_uom': product.uom_id.id,
994 # FIXME must depend on uos/uom of the product and not only of the coeff.
997 'product_uom_qty': product_uos_qty / product.uos_coeff,
998 'th_weight': product_uos_qty / product.uos_coeff * product.weight
1000 except ZeroDivisionError:
1002 return {'value': value}
1004 def copy_data(self, cr, uid, id, default=None, context=None):
1007 default.update({'state': 'draft', 'move_ids': [], 'invoiced': False, 'invoice_lines': []})
1008 return super(sale_order_line, self).copy_data(cr, uid, id, default, context=context)
1010 def product_id_change(self, cr, uid, ids, pricelist, product, qty=0,
1011 uom=False, qty_uos=0, uos=False, name='', partner_id=False,
1012 lang=False, update_tax=True, date_order=False, packaging=False, fiscal_position=False, flag=False):
1014 raise osv.except_osv(_('No Customer Defined !'), _('You have to select a customer in the sales form !\nPlease set one customer before choosing a product.'))
1016 product_uom_obj = self.pool.get('product.uom')
1017 partner_obj = self.pool.get('res.partner')
1018 product_obj = self.pool.get('product.product')
1020 lang = partner_obj.browse(cr, uid, partner_id).lang
1021 context = {'lang': lang, 'partner_id': partner_id}
1024 return {'value': {'th_weight': 0, 'product_packaging': False,
1025 'product_uos_qty': qty}, 'domain': {'product_uom': [],
1028 date_order = time.strftime('%Y-%m-%d')
1031 product_obj = product_obj.browse(cr, uid, product, context=context)
1032 if not packaging and product_obj.packaging:
1033 packaging = product_obj.packaging[0].id
1034 result['product_packaging'] = packaging
1037 default_uom = product_obj.uom_id and product_obj.uom_id.id
1038 pack = self.pool.get('product.packaging').browse(cr, uid, packaging, context=context)
1039 q = product_uom_obj._compute_qty(cr, uid, uom, pack.qty, default_uom)
1040 # qty = qty - qty % q + q
1041 if qty and (q and not (qty % q) == 0):
1042 ean = pack.ean or _('(n/a)')
1045 warn_msg = _("You selected a quantity of %d Units.\n"
1046 "But it's not compatible with the selected packaging.\n"
1047 "Here is a proposition of quantities according to the packaging:\n\n"
1048 "EAN: %s Quantity: %s Type of ul: %s") % \
1049 (qty, ean, qty_pack, type_ul.name)
1051 'title': _('Picking Information !'),
1054 result['product_uom_qty'] = qty
1058 uom2 = product_uom_obj.browse(cr, uid, uom)
1059 if product_obj.uom_id.category_id.id != uom2.category_id.id:
1062 if product_obj.uos_id:
1063 uos2 = product_uom_obj.browse(cr, uid, uos)
1064 if product_obj.uos_id.category_id.id != uos2.category_id.id:
1068 if product_obj.description_sale:
1069 result['notes'] = product_obj.description_sale
1070 fpos = fiscal_position and self.pool.get('account.fiscal.position').browse(cr, uid, fiscal_position) or False
1071 if update_tax: #The quantity only have changed
1072 result['delay'] = (product_obj.sale_delay or 0.0)
1073 result['tax_id'] = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, product_obj.taxes_id)
1074 result.update({'type': product_obj.procure_method})
1077 result['name'] = self.pool.get('product.product').name_get(cr, uid, [product_obj.id], context=context)[0][1]
1079 if (not uom) and (not uos):
1080 result['product_uom'] = product_obj.uom_id.id
1081 if product_obj.uos_id:
1082 result['product_uos'] = product_obj.uos_id.id
1083 result['product_uos_qty'] = qty * product_obj.uos_coeff
1084 uos_category_id = product_obj.uos_id.category_id.id
1086 result['product_uos'] = False
1087 result['product_uos_qty'] = qty
1088 uos_category_id = False
1089 result['th_weight'] = qty * product_obj.weight
1090 domain = {'product_uom':
1091 [('category_id', '=', product_obj.uom_id.category_id.id)],
1093 [('category_id', '=', uos_category_id)]}
1095 elif uos and not uom: # only happens if uom is False
1096 result['product_uom'] = product_obj.uom_id and product_obj.uom_id.id
1097 result['product_uom_qty'] = qty_uos / product_obj.uos_coeff
1098 result['th_weight'] = result['product_uom_qty'] * product_obj.weight
1099 elif uom: # whether uos is set or not
1100 default_uom = product_obj.uom_id and product_obj.uom_id.id
1101 q = product_uom_obj._compute_qty(cr, uid, uom, qty, default_uom)
1102 if product_obj.uos_id:
1103 result['product_uos'] = product_obj.uos_id.id
1104 result['product_uos_qty'] = qty * product_obj.uos_coeff
1106 result['product_uos'] = False
1107 result['product_uos_qty'] = qty
1108 result['th_weight'] = q * product_obj.weight # Round the quantity up
1111 uom2 = product_obj.uom_id
1112 if (product_obj.type=='product') and (product_obj.virtual_available * uom2.factor < qty * product_obj.uom_id.factor) \
1113 and (product_obj.procure_method=='make_to_stock'):
1115 'title': _('Not enough stock !'),
1116 'message': _('You plan to sell %.2f %s but you only have %.2f %s available !\nThe real stock is %.2f %s. (without reservations)') %
1117 (qty, uom2 and uom2.name or product_obj.uom_id.name,
1118 max(0,product_obj.virtual_available), product_obj.uom_id.name,
1119 max(0,product_obj.qty_available), product_obj.uom_id.name)
1124 'title': 'No Pricelist !',
1126 'You have to select a pricelist or a customer in the sales form !\n'
1127 'Please set one before choosing a product.'
1130 price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist],
1131 product, qty or 1.0, partner_id, {
1137 'title': 'No valid pricelist line found !',
1139 "Couldn't find a pricelist line matching this product and quantity.\n"
1140 "You have to change either the product, the quantity or the pricelist."
1143 result.update({'price_unit': price})
1144 return {'value': result, 'domain': domain, 'warning': warning}
1146 def product_uom_change(self, cursor, user, ids, pricelist, product, qty=0,
1147 uom=False, qty_uos=0, uos=False, name='', partner_id=False,
1148 lang=False, update_tax=True, date_order=False):
1149 res = self.product_id_change(cursor, user, ids, pricelist, product,
1150 qty=qty, uom=uom, qty_uos=qty_uos, uos=uos, name=name,
1151 partner_id=partner_id, lang=lang, update_tax=update_tax,
1152 date_order=date_order)
1153 if 'product_uom' in res['value']:
1154 del res['value']['product_uom']
1156 res['value']['price_unit'] = 0.0
1159 def unlink(self, cr, uid, ids, context=None):
1162 """Allows to delete sales order lines in draft,cancel states"""
1163 for rec in self.browse(cr, uid, ids, context=context):
1164 if rec.state not in ['draft', 'cancel']:
1165 raise osv.except_osv(_('Invalid action !'), _('Cannot delete a sales order line which is %s !') %(rec.state,))
1166 return super(sale_order_line, self).unlink(cr, uid, ids, context=context)
1170 class sale_config_picking_policy(osv.osv_memory):
1171 _name = 'sale.config.picking_policy'
1172 _inherit = 'res.config'
1175 'name': fields.char('Name', size=64),
1176 'picking_policy': fields.selection([
1177 ('direct', 'Direct Delivery'),
1178 ('one', 'All at Once')
1179 ], '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.."),
1180 'order_policy': fields.selection([
1181 ('manual', 'Invoice Based on Sales Orders'),
1182 ('picking', 'Invoice Based on Deliveries'),
1183 ], 'Shipping Default Policy', required=True,
1184 help="You can generate invoices based on sales orders or based on shippings."),
1185 'step': fields.selection([
1186 ('one', 'Delivery Order Only'),
1187 ('two', 'Picking List & Delivery Order')
1188 ], 'Steps To Deliver a Sales Order', required=True,
1189 help="By default, OpenERP is able to manage complex routing and paths "\
1190 "of products in your warehouse and partner locations. This will configure "\
1191 "the most common and simple methods to deliver products to the customer "\
1192 "in one or two operations by the worker.")
1195 'picking_policy': 'direct',
1196 'order_policy': 'manual',
1200 def execute(self, cr, uid, ids, context=None):
1201 for o in self.browse(cr, uid, ids, context=context):
1202 ir_values_obj = self.pool.get('ir.values')
1203 ir_values_obj.set(cr, uid, 'default', False, 'picking_policy', ['sale.order'], o.picking_policy)
1204 ir_values_obj.set(cr, uid, 'default', False, 'order_policy', ['sale.order'], o.order_policy)
1206 md = self.pool.get('ir.model.data')
1207 location_id = md.get_object_reference(cr, uid, 'stock', 'stock_location_output')
1208 location_id = location_id and location_id[1] or False
1209 self.pool.get('stock.location').write(cr, uid, [location_id], {'chained_auto_packing': 'manual'})
1211 sale_config_picking_policy()
1213 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: