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):
97 tmp[id] = {'picked': 0.0, 'total': 0.0}
99 p.sale_id as sale_order_id, sum(m.product_qty) as nbr, mp.state as procurement_state, m.state as move_state, p.type as picking_type
103 stock_picking p on (p.id=m.picking_id)
105 procurement_order mp on (mp.move_id=m.id)
107 p.sale_id IN %s GROUP BY m.state, mp.state, p.sale_id, p.type''', (tuple(ids),))
109 for item in cr.dictfetchall():
110 if item['move_state'] == 'cancel':
113 if item['picking_type'] == 'in':#this is a returned picking
114 tmp[item['sale_order_id']]['total'] -= item['nbr'] or 0.0 # Deducting the return picking qty
115 if item['procurement_state'] == 'done' or item['move_state'] == 'done':
116 tmp[item['sale_order_id']]['picked'] -= item['nbr'] or 0.0
118 tmp[item['sale_order_id']]['total'] += item['nbr'] or 0.0
119 if item['procurement_state'] == 'done' or item['move_state'] == 'done':
120 tmp[item['sale_order_id']]['picked'] += item['nbr'] or 0.0
122 for order in self.browse(cr, uid, ids, context=context):
124 res[order.id] = 100.0
126 res[order.id] = tmp[order.id]['total'] and (100.0 * tmp[order.id]['picked'] / tmp[order.id]['total']) or 0.0
129 def _invoiced_rate(self, cursor, user, ids, name, arg, context=None):
131 for sale in self.browse(cursor, user, ids, context=context):
136 for invoice in sale.invoice_ids:
137 if invoice.state not in ('draft', 'cancel'):
138 tot += invoice.amount_untaxed
140 res[sale.id] = min(100.0, tot * 100.0 / (sale.amount_untaxed or 1.00))
145 def _invoiced(self, cursor, user, ids, name, arg, context=None):
147 for sale in self.browse(cursor, user, ids, context=context):
149 for invoice in sale.invoice_ids:
150 if invoice.state != 'paid':
153 if not sale.invoice_ids:
157 def _invoiced_search(self, cursor, user, obj, name, args, context=None):
166 clause += 'AND inv.state = \'paid\''
168 clause += 'AND inv.state != \'cancel\' AND sale.state != \'cancel\' AND inv.state <> \'paid\' AND rel.order_id = sale.id '
169 sale_clause = ', sale_order AS sale '
172 cursor.execute('SELECT rel.order_id ' \
173 'FROM sale_order_invoice_rel AS rel, account_invoice AS inv '+ sale_clause + \
174 'WHERE rel.invoice_id = inv.id ' + clause)
175 res = cursor.fetchall()
177 cursor.execute('SELECT sale.id ' \
178 'FROM sale_order AS sale ' \
179 'WHERE sale.id NOT IN ' \
180 '(SELECT rel.order_id ' \
181 'FROM sale_order_invoice_rel AS rel) and sale.state != \'cancel\'')
182 res.extend(cursor.fetchall())
184 return [('id', '=', 0)]
185 return [('id', 'in', [x[0] for x in res])]
187 def _get_order(self, cr, uid, ids, context=None):
189 for line in self.pool.get('sale.order.line').browse(cr, uid, ids, context=context):
190 result[line.order_id.id] = True
194 'name': fields.char('Order Reference', size=64, required=True,
195 readonly=True, states={'draft': [('readonly', False)]}, select=True),
196 'shop_id': fields.many2one('sale.shop', 'Shop', required=True, readonly=True, states={'draft': [('readonly', False)]}),
197 'origin': fields.char('Source Document', size=64, help="Reference of the document that generated this sales order request."),
198 'client_order_ref': fields.char('Customer Reference', size=64),
199 'state': fields.selection([
200 ('draft', 'Quotation'),
201 ('waiting_date', 'Waiting Schedule'),
202 ('manual', 'Manual In Progress'),
203 ('progress', 'In Progress'),
204 ('shipping_except', 'Shipping Exception'),
205 ('invoice_except', 'Invoice Exception'),
207 ('cancel', 'Cancelled')
208 ], '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),
209 'date_order': fields.date('Ordered Date', required=True, readonly=True, select=True, states={'draft': [('readonly', False)]}),
210 'create_date': fields.date('Creation Date', readonly=True, select=True, help="Date on which sales order is created."),
211 'date_confirm': fields.date('Confirmation Date', readonly=True, select=True, help="Date on which sales order is confirmed."),
212 'user_id': fields.many2one('res.users', 'Salesman', states={'draft': [('readonly', False)]}, select=True),
213 'partner_id': fields.many2one('res.partner', 'Customer', readonly=True, states={'draft': [('readonly', False)]}, required=True, change_default=True, select=True),
214 '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."),
215 '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."),
216 '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."),
218 '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."),
219 'picking_policy': fields.selection([('direct', 'Partial Delivery'), ('one', 'Complete Delivery')],
220 '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?"""),
221 'order_policy': fields.selection([
222 ('prepaid', 'Payment Before Delivery'),
223 ('manual', 'Shipping & Manual Invoice'),
224 ('postpaid', 'Invoice On Order After Delivery'),
225 ('picking', 'Invoice From The Picking'),
226 ], 'Shipping Policy', required=True, readonly=True, states={'draft': [('readonly', False)]},
227 help="""The Shipping Policy is used to synchronise invoice and delivery operations.
228 - The 'Pay Before delivery' choice will first generate the invoice and then generate the picking order after the payment of this invoice.
229 - 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.
230 - The 'Invoice On Order After Delivery' choice will generate the draft invoice based on sales order after all picking lists have been finished.
231 - The 'Invoice From The Picking' choice is used to create an invoice during the picking process."""),
232 'pricelist_id': fields.many2one('product.pricelist', 'Pricelist', required=True, readonly=True, states={'draft': [('readonly', False)]}, help="Pricelist for current sales order."),
233 'project_id': fields.many2one('account.analytic.account', 'Analytic Account', readonly=True, states={'draft': [('readonly', False)]}, help="The analytic account related to a sales order."),
235 'order_line': fields.one2many('sale.order.line', 'order_id', 'Order Lines', readonly=True, states={'draft': [('readonly', False)]}),
236 '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)."),
237 '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."),
238 '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."),
239 'picked_rate': fields.function(_picked_rate, method=True, string='Picked', type='float'),
240 'invoiced_rate': fields.function(_invoiced_rate, method=True, string='Invoiced', type='float'),
241 'invoiced': fields.function(_invoiced, method=True, string='Paid',
242 fnct_search=_invoiced_search, type='boolean', help="It indicates that an invoice has been paid."),
243 'note': fields.text('Notes'),
245 'amount_untaxed': fields.function(_amount_all, method=True, digits_compute= dp.get_precision('Sale Price'), string='Untaxed Amount',
247 'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line'], 10),
248 'sale.order.line': (_get_order, ['price_unit', 'tax_id', 'discount', 'product_uom_qty'], 10),
250 multi='sums', help="The amount without tax."),
251 'amount_tax': fields.function(_amount_all, method=True, digits_compute= dp.get_precision('Sale Price'), string='Taxes',
253 'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line'], 10),
254 'sale.order.line': (_get_order, ['price_unit', 'tax_id', 'discount', 'product_uom_qty'], 10),
256 multi='sums', help="The tax amount."),
257 'amount_total': fields.function(_amount_all, method=True, digits_compute= dp.get_precision('Sale Price'), string='Total',
259 'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line'], 10),
260 'sale.order.line': (_get_order, ['price_unit', 'tax_id', 'discount', 'product_uom_qty'], 10),
262 multi='sums', help="The total amount."),
264 '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)]}),
265 'payment_term': fields.many2one('account.payment.term', 'Payment Term'),
266 'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position'),
267 'company_id': fields.related('shop_id','company_id',type='many2one',relation='res.company',string='Company',store=True,readonly=True)
270 'picking_policy': 'direct',
271 'date_order': lambda *a: time.strftime('%Y-%m-%d'),
272 'order_policy': 'manual',
274 'user_id': lambda obj, cr, uid, context: uid,
275 'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'sale.order'),
276 'invoice_quantity': 'order',
277 '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'],
278 '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'],
279 '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'],
282 ('name_uniq', 'unique(name)', 'Order Reference must be unique !'),
287 def unlink(self, cr, uid, ids, context=None):
288 sale_orders = self.read(cr, uid, ids, ['state'], context=context)
290 for s in sale_orders:
291 if s['state'] in ['draft', 'cancel']:
292 unlink_ids.append(s['id'])
294 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Sales Order(s) which are already confirmed !'))
295 return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
297 def onchange_shop_id(self, cr, uid, ids, shop_id):
300 shop = self.pool.get('sale.shop').browse(cr, uid, shop_id)
301 v['project_id'] = shop.project_id.id
302 # Que faire si le client a une pricelist a lui ?
303 if shop.pricelist_id.id:
304 v['pricelist_id'] = shop.pricelist_id.id
307 def action_cancel_draft(self, cr, uid, ids, *args):
310 cr.execute('select id from sale_order_line where order_id IN %s and state=%s', (tuple(ids), 'cancel'))
311 line_ids = map(lambda x: x[0], cr.fetchall())
312 self.write(cr, uid, ids, {'state': 'draft', 'invoice_ids': [], 'shipped': 0})
313 self.pool.get('sale.order.line').write(cr, uid, line_ids, {'invoiced': False, 'state': 'draft', 'invoice_lines': [(6, 0, [])]})
314 wf_service = netsvc.LocalService("workflow")
316 # Deleting the existing instance of workflow for SO
317 wf_service.trg_delete(uid, 'sale.order', inv_id, cr)
318 wf_service.trg_create(uid, 'sale.order', inv_id, cr)
319 for (id,name) in self.name_get(cr, uid, ids):
320 message = _("The sales order '%s' has been set in draft state.") %(name,)
321 self.log(cr, uid, id, message)
324 def onchange_partner_id(self, cr, uid, ids, part):
326 return {'value': {'partner_invoice_id': False, 'partner_shipping_id': False, 'partner_order_id': False, 'payment_term': False, 'fiscal_position': False}}
328 addr = self.pool.get('res.partner').address_get(cr, uid, [part], ['delivery', 'invoice', 'contact'])
329 part = self.pool.get('res.partner').browse(cr, uid, part)
330 pricelist = part.property_product_pricelist and part.property_product_pricelist.id or False
331 payment_term = part.property_payment_term and part.property_payment_term.id or False
332 fiscal_position = part.property_account_position and part.property_account_position.id or False
333 dedicated_salesman = part.user_id and part.user_id.id or uid
335 'partner_invoice_id': addr['invoice'],
336 'partner_order_id': addr['contact'],
337 'partner_shipping_id': addr['delivery'],
338 'payment_term': payment_term,
339 'fiscal_position': fiscal_position,
340 'user_id': dedicated_salesman,
343 val['pricelist_id'] = pricelist
344 return {'value': val}
346 def shipping_policy_change(self, cr, uid, ids, policy, context=None):
350 if policy == 'prepaid':
352 elif policy == 'picking':
353 inv_qty = 'procurement'
354 return {'value': {'invoice_quantity': inv_qty}}
356 def write(self, cr, uid, ids, vals, context=None):
357 if vals.get('order_policy', False):
358 if vals['order_policy'] == 'prepaid':
359 vals.update({'invoice_quantity': 'order'})
360 elif vals['order_policy'] == 'picking':
361 vals.update({'invoice_quantity': 'procurement'})
362 return super(sale_order, self).write(cr, uid, ids, vals, context=context)
364 def create(self, cr, uid, vals, context=None):
365 if vals.get('order_policy', False):
366 if vals['order_policy'] == 'prepaid':
367 vals.update({'invoice_quantity': 'order'})
368 if vals['order_policy'] == 'picking':
369 vals.update({'invoice_quantity': 'procurement'})
370 return super(sale_order, self).create(cr, uid, vals, context=context)
372 def button_dummy(self, cr, uid, ids, context=None):
375 #FIXME: the method should return the list of invoices created (invoice_ids)
376 # and not the id of the last invoice created (res). The problem is that we
377 # cannot change it directly since the method is called by the sales order
378 # workflow and I suppose it expects a single id...
379 def _inv_get(self, cr, uid, order, context=None):
382 def _make_invoice(self, cr, uid, order, lines, context=None):
383 journal_obj = self.pool.get('account.journal')
384 inv_obj = self.pool.get('account.invoice')
385 obj_invoice_line = self.pool.get('account.invoice.line')
389 journal_ids = journal_obj.search(cr, uid, [('type', '=', 'sale'), ('company_id', '=', order.company_id.id)], limit=1)
391 raise osv.except_osv(_('Error !'),
392 _('There is no sales journal defined for this company: "%s" (id:%d)') % (order.company_id.name, order.company_id.id))
393 a = order.partner_id.property_account_receivable.id
394 pay_term = order.payment_term and order.payment_term.id or False
395 invoiced_sale_line_ids = self.pool.get('sale.order.line').search(cr, uid, [('order_id', '=', order.id), ('invoiced', '=', True)], context=context)
396 from_line_invoice_ids = []
397 for invoiced_sale_line_id in self.pool.get('sale.order.line').browse(cr, uid, invoiced_sale_line_ids, context=context):
398 for invoice_line_id in invoiced_sale_line_id.invoice_lines:
399 if invoice_line_id.invoice_id.id not in from_line_invoice_ids:
400 from_line_invoice_ids.append(invoice_line_id.invoice_id.id)
401 for preinv in order.invoice_ids:
402 if preinv.state not in ('cancel',) and preinv.id not in from_line_invoice_ids:
403 for preline in preinv.invoice_line:
404 inv_line_id = obj_invoice_line.copy(cr, uid, preline.id, {'invoice_id': False, 'price_unit': -preline.price_unit})
405 lines.append(inv_line_id)
407 'name': order.client_order_ref or '',
408 'origin': order.name,
409 'type': 'out_invoice',
410 'reference': order.client_order_ref or order.name,
412 'partner_id': order.partner_id.id,
413 'journal_id': journal_ids[0],
414 'address_invoice_id': order.partner_invoice_id.id,
415 'address_contact_id': order.partner_order_id.id,
416 'invoice_line': [(6, 0, lines)],
417 'currency_id': order.pricelist_id.currency_id.id,
418 'comment': order.note,
419 'payment_term': pay_term,
420 'fiscal_position': order.fiscal_position.id or order.partner_id.property_account_position.id,
421 'date_invoice': context.get('date_invoice',False),
422 'company_id': order.company_id.id,
423 'user_id': order.user_id and order.user_id.id or False
425 inv.update(self._inv_get(cr, uid, order))
426 inv_id = inv_obj.create(cr, uid, inv, context=context)
427 data = inv_obj.onchange_payment_term_date_invoice(cr, uid, [inv_id], pay_term, time.strftime('%Y-%m-%d'))
428 if data.get('value', False):
429 inv_obj.write(cr, uid, [inv_id], data['value'], context=context)
430 inv_obj.button_compute(cr, uid, [inv_id])
433 def manual_invoice(self, cr, uid, ids, context=None):
434 mod_obj = self.pool.get('ir.model.data')
435 wf_service = netsvc.LocalService("workflow")
439 for record in self.pool.get('sale.order').browse(cr, uid, id).invoice_ids:
440 inv_ids.add(record.id)
441 # inv_ids would have old invoices if any
443 wf_service.trg_validate(uid, 'sale.order', id, 'manual_invoice', cr)
444 for record in self.pool.get('sale.order').browse(cr, uid, id).invoice_ids:
445 inv_ids1.add(record.id)
446 inv_ids = list(inv_ids1.difference(inv_ids))
448 res = mod_obj.get_object_reference(cr, uid, 'account', 'invoice_form')
449 res_id = res and res[1] or False,
452 'name': 'Customer Invoices',
456 'res_model': 'account.invoice',
457 'context': "{'type':'out_invoice'}",
458 'type': 'ir.actions.act_window',
461 'res_id': inv_ids and inv_ids[0] or False,
464 def action_invoice_create(self, cr, uid, ids, grouped=False, states=['confirmed', 'done', 'exception'], date_inv = False, context=None):
468 picking_obj = self.pool.get('stock.picking')
469 invoice = self.pool.get('account.invoice')
470 obj_sale_order_line = self.pool.get('sale.order.line')
473 # If date was specified, use it as date invoiced, usefull when invoices are generated this month and put the
474 # last day of the last month as invoice date
476 context['date_inv'] = date_inv
477 for o in self.browse(cr, uid, ids, context=context):
479 for line in o.order_line:
482 elif (line.state in states):
483 lines.append(line.id)
484 created_lines = obj_sale_order_line.invoice_line_create(cr, uid, lines)
486 invoices.setdefault(o.partner_id.id, []).append((o, created_lines))
488 for o in self.browse(cr, uid, ids, context=context):
489 for i in o.invoice_ids:
490 if i.state == 'draft':
492 for val in invoices.values():
494 res = self._make_invoice(cr, uid, val[0][0], reduce(lambda x, y: x + y, [l for o, l in val], []), context=context)
497 invoice_ref += o.name + '|'
498 self.write(cr, uid, [o.id], {'state': 'progress'})
499 if o.order_policy == 'picking':
500 picking_obj.write(cr, uid, map(lambda x: x.id, o.picking_ids), {'invoice_state': 'invoiced'})
501 cr.execute('insert into sale_order_invoice_rel (order_id,invoice_id) values (%s,%s)', (o.id, res))
502 invoice.write(cr, uid, [res], {'origin': invoice_ref, 'name': invoice_ref})
504 for order, il in val:
505 res = self._make_invoice(cr, uid, order, il, context=context)
506 invoice_ids.append(res)
507 self.write(cr, uid, [order.id], {'state': 'progress'})
508 if order.order_policy == 'picking':
509 picking_obj.write(cr, uid, map(lambda x: x.id, order.picking_ids), {'invoice_state': 'invoiced'})
510 cr.execute('insert into sale_order_invoice_rel (order_id,invoice_id) values (%s,%s)', (order.id, res))
513 def action_invoice_cancel(self, cr, uid, ids, context=None):
516 for sale in self.browse(cr, uid, ids, context=context):
517 for line in sale.order_line:
519 # Check if the line is invoiced (has asociated invoice
520 # lines from non-cancelled invoices).
523 for iline in line.invoice_lines:
524 if iline.invoice_id and iline.invoice_id.state != 'cancel':
527 # Update the line (only when needed)
528 if line.invoiced != invoiced:
529 self.pool.get('sale.order.line').write(cr, uid, [line.id], {'invoiced': invoiced}, context=context)
530 self.write(cr, uid, ids, {'state': 'invoice_except', 'invoice_ids': False}, context=context)
533 def action_invoice_end(self, cr, uid, ids, context=None):
534 for order in self.browse(cr, uid, ids, context=context):
536 # Update the sale order lines state (and invoiced flag).
538 for line in order.order_line:
541 # Check if the line is invoiced (has asociated invoice
542 # lines from non-cancelled invoices).
545 for iline in line.invoice_lines:
546 if iline.invoice_id and iline.invoice_id.state != 'cancel':
549 if line.invoiced != invoiced:
550 vals['invoiced'] = invoiced
551 # If the line was in exception state, now it gets confirmed.
552 if line.state == 'exception':
553 vals['state'] = 'confirmed'
554 # Update the line (only when needed).
556 self.pool.get('sale.order.line').write(cr, uid, [line.id], vals, context=context)
558 # Update the sales order state.
560 if order.state == 'invoice_except':
561 self.write(cr, uid, [order.id], {'state': 'progress'}, context=context)
564 def action_cancel(self, cr, uid, ids, context=None):
565 wf_service = netsvc.LocalService("workflow")
568 sale_order_line_obj = self.pool.get('sale.order.line')
569 proc_obj = self.pool.get('procurement.order')
570 for sale in self.browse(cr, uid, ids, context=context):
571 for pick in sale.picking_ids:
572 if pick.state not in ('draft', 'cancel'):
573 raise osv.except_osv(
574 _('Could not cancel sales order !'),
575 _('You must first cancel all picking attached to this sales order.'))
576 if pick.state == 'cancel':
577 for mov in pick.move_lines:
578 proc_ids = proc_obj.search(cr, uid, [('move_id', '=', mov.id)])
580 for proc in proc_ids:
581 wf_service.trg_validate(uid, 'procurement.order', proc, 'button_check', cr)
582 for r in self.read(cr, uid, ids, ['picking_ids']):
583 for pick in r['picking_ids']:
584 wf_service.trg_validate(uid, 'stock.picking', pick, 'button_cancel', cr)
585 for inv in sale.invoice_ids:
586 if inv.state not in ('draft', 'cancel'):
587 raise osv.except_osv(
588 _('Could not cancel this sales order !'),
589 _('You must first cancel all invoices attached to this sales order.'))
590 for r in self.read(cr, uid, ids, ['invoice_ids']):
591 for inv in r['invoice_ids']:
592 wf_service.trg_validate(uid, 'account.invoice', inv, 'invoice_cancel', cr)
593 sale_order_line_obj.write(cr, uid, [l.id for l in sale.order_line],
595 message = _("The sales order '%s' has been cancelled.") % (sale.name,)
596 self.log(cr, uid, sale.id, message)
597 self.write(cr, uid, ids, {'state': 'cancel'})
600 def action_wait(self, cr, uid, ids, *args):
601 for o in self.browse(cr, uid, ids):
602 if (o.order_policy == 'manual'):
603 self.write(cr, uid, [o.id], {'state': 'manual', 'date_confirm': time.strftime('%Y-%m-%d')})
605 self.write(cr, uid, [o.id], {'state': 'progress', 'date_confirm': time.strftime('%Y-%m-%d')})
606 self.pool.get('sale.order.line').button_confirm(cr, uid, [x.id for x in o.order_line])
607 message = _("The quotation '%s' has been converted to a sales order.") % (o.name,)
608 self.log(cr, uid, o.id, message)
611 def procurement_lines_get(self, cr, uid, ids, *args):
613 for order in self.browse(cr, uid, ids, context={}):
614 for line in order.order_line:
615 if line.procurement_id:
616 res.append(line.procurement_id.id)
619 # if mode == 'finished':
620 # returns True if all lines are done, False otherwise
621 # if mode == 'canceled':
622 # returns True if there is at least one canceled line, False otherwise
623 def test_state(self, cr, uid, ids, mode, *args):
624 assert mode in ('finished', 'canceled'), _("invalid mode for test_state")
629 write_cancel_ids = []
630 for order in self.browse(cr, uid, ids, context={}):
631 for line in order.order_line:
632 if (not line.procurement_id) or (line.procurement_id.state=='done'):
633 if line.state != 'done':
634 write_done_ids.append(line.id)
637 if line.procurement_id:
638 if (line.procurement_id.state == 'cancel'):
640 if line.state != 'exception':
641 write_cancel_ids.append(line.id)
645 self.pool.get('sale.order.line').write(cr, uid, write_done_ids, {'state': 'done'})
647 self.pool.get('sale.order.line').write(cr, uid, write_cancel_ids, {'state': 'exception'})
649 if mode == 'finished':
651 elif mode == 'canceled':
657 def action_ship_create(self, cr, uid, ids, *args):
658 wf_service = netsvc.LocalService("workflow")
660 move_obj = self.pool.get('stock.move')
661 proc_obj = self.pool.get('procurement.order')
662 company = self.pool.get('res.users').browse(cr, uid, uid).company_id
663 for order in self.browse(cr, uid, ids, context={}):
665 output_id = order.shop_id.warehouse_id.lot_output_id.id
667 for line in order.order_line:
669 date_planned = datetime.now() + relativedelta(days=line.delay or 0.0)
670 date_planned = (date_planned - timedelta(days=company.security_lead)).strftime('%Y-%m-%d %H:%M:%S')
672 if line.state == 'done':
675 if line.product_id and line.product_id.product_tmpl_id.type in ('product', 'consu'):
676 location_id = order.shop_id.warehouse_id.lot_stock_id.id
678 pick_name = self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.out')
679 picking_id = self.pool.get('stock.picking').create(cr, uid, {
681 'origin': order.name,
684 'move_type': order.picking_policy,
686 'address_id': order.partner_shipping_id.id,
688 'invoice_state': (order.order_policy=='picking' and '2binvoiced') or 'none',
689 'company_id': order.company_id.id,
691 move_id = self.pool.get('stock.move').create(cr, uid, {
692 'name': line.name[:64],
693 'picking_id': picking_id,
694 'product_id': line.product_id.id,
695 'date': date_planned,
696 'date_expected': date_planned,
697 'product_qty': line.product_uom_qty,
698 'product_uom': line.product_uom.id,
699 'product_uos_qty': line.product_uos_qty,
700 'product_uos': (line.product_uos and line.product_uos.id)\
701 or line.product_uom.id,
702 'product_packaging': line.product_packaging.id,
703 'address_id': line.address_allotment_id.id or order.partner_shipping_id.id,
704 'location_id': location_id,
705 'location_dest_id': output_id,
706 'sale_line_id': line.id,
707 'tracking_id': False,
711 'company_id': order.company_id.id,
715 proc_id = self.pool.get('procurement.order').create(cr, uid, {
717 'origin': order.name,
718 'date_planned': date_planned,
719 'product_id': line.product_id.id,
720 'product_qty': line.product_uom_qty,
721 'product_uom': line.product_uom.id,
722 'product_uos_qty': (line.product_uos and line.product_uos_qty)\
723 or line.product_uom_qty,
724 'product_uos': (line.product_uos and line.product_uos.id)\
725 or line.product_uom.id,
726 'location_id': order.shop_id.warehouse_id.lot_stock_id.id,
727 'procure_method': line.type,
729 'property_ids': [(6, 0, [x.id for x in line.property_ids])],
730 'company_id': order.company_id.id,
732 proc_ids.append(proc_id)
733 self.pool.get('sale.order.line').write(cr, uid, [line.id], {'procurement_id': proc_id})
734 if order.state == 'shipping_except':
735 for pick in order.picking_ids:
736 for move in pick.move_lines:
737 if move.state == 'cancel':
738 mov_ids = move_obj.search(cr, uid, [('state', '=', 'cancel'),('sale_line_id', '=', line.id),('picking_id', '=', pick.id)])
740 for mov in move_obj.browse(cr, uid, mov_ids):
741 move_obj.write(cr, uid, [move_id], {'product_qty': mov.product_qty, 'product_uos_qty': mov.product_uos_qty})
742 proc_obj.write(cr, uid, [proc_id], {'product_qty': mov.product_qty, 'product_uos_qty': mov.product_uos_qty})
747 wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
749 for proc_id in proc_ids:
750 wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_confirm', cr)
752 if order.state == 'shipping_except':
753 val['state'] = 'progress'
754 val['shipped'] = False
756 if (order.order_policy == 'manual'):
757 for line in order.order_line:
758 if (not line.invoiced) and (line.state not in ('cancel', 'draft')):
759 val['state'] = 'manual'
761 self.write(cr, uid, [order.id], val)
764 def action_ship_end(self, cr, uid, ids, context=None):
765 for order in self.browse(cr, uid, ids, context=context):
766 val = {'shipped': True}
767 if order.state == 'shipping_except':
768 val['state'] = 'progress'
769 if (order.order_policy == 'manual'):
770 for line in order.order_line:
771 if (not line.invoiced) and (line.state not in ('cancel', 'draft')):
772 val['state'] = 'manual'
774 for line in order.order_line:
776 if line.state == 'exception':
777 towrite.append(line.id)
779 self.pool.get('sale.order.line').write(cr, uid, towrite, {'state': 'done'}, context=context)
780 self.write(cr, uid, [order.id], val)
783 def _log_event(self, cr, uid, ids, factor=0.7, name='Open Order'):
784 invs = self.read(cr, uid, ids, ['date_order', 'partner_id', 'amount_untaxed'])
786 part = inv['partner_id'] and inv['partner_id'][0]
787 pr = inv['amount_untaxed'] or 0.0
788 partnertype = 'customer'
791 'name': 'Order: '+name,
793 'description': 'Order '+str(inv['id']),
796 'date': time.strftime('%Y-%m-%d'),
799 'partner_type': partnertype,
801 'planned_revenue': pr,
805 self.pool.get('res.partner.event').create(cr, uid, event)
807 def has_stockable_products(self, cr, uid, ids, *args):
808 for order in self.browse(cr, uid, ids):
809 for order_line in order.order_line:
810 if order_line.product_id and order_line.product_id.product_tmpl_id.type in ('product', 'consu'):
815 # TODO add a field price_unit_uos
816 # - update it on change product and unit price
817 # - use it in report if there is a uos
818 class sale_order_line(osv.osv):
820 def _amount_line(self, cr, uid, ids, field_name, arg, context=None):
821 tax_obj = self.pool.get('account.tax')
822 cur_obj = self.pool.get('res.currency')
826 for line in self.browse(cr, uid, ids, context=context):
827 price = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
828 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)
829 cur = line.order_id.pricelist_id.currency_id
830 res[line.id] = cur_obj.round(cr, uid, cur, taxes['total'])
833 def _number_packages(self, cr, uid, ids, field_name, arg, context=None):
835 for line in self.browse(cr, uid, ids, context=context):
837 res[line.id] = int((line.product_uom_qty+line.product_packaging.qty-0.0001) / line.product_packaging.qty)
842 _name = 'sale.order.line'
843 _description = 'Sales Order Line'
845 'order_id': fields.many2one('sale.order', 'Order Reference', required=True, ondelete='cascade', select=True, readonly=True, states={'draft':[('readonly',False)]}),
846 'name': fields.char('Description', size=256, required=True, select=True, readonly=True, states={'draft': [('readonly', False)]}),
847 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of sales order lines."),
848 '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)]}),
849 'product_id': fields.many2one('product.product', 'Product', domain=[('sale_ok', '=', True)], change_default=True),
850 'invoice_lines': fields.many2many('account.invoice.line', 'sale_order_line_invoice_rel', 'order_line_id', 'invoice_id', 'Invoice Lines', readonly=True),
851 'invoiced': fields.boolean('Invoiced', readonly=True),
852 'procurement_id': fields.many2one('procurement.order', 'Procurement'),
853 'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Sale Price'), readonly=True, states={'draft': [('readonly', False)]}),
854 'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal', digits_compute= dp.get_precision('Sale Price')),
855 'tax_id': fields.many2many('account.tax', 'sale_order_tax', 'order_line_id', 'tax_id', 'Taxes', readonly=True, states={'draft': [('readonly', False)]}),
856 'type': fields.selection([('make_to_stock', 'from stock'), ('make_to_order', 'on order')], 'Procurement Method', required=True, readonly=True, states={'draft': [('readonly', False)]}),
857 'property_ids': fields.many2many('mrp.property', 'sale_order_line_property_rel', 'order_id', 'property_id', 'Properties', readonly=True, states={'draft': [('readonly', False)]}),
858 'address_allotment_id': fields.many2one('res.partner.address', 'Allotment Partner'),
859 'product_uom_qty': fields.float('Quantity (UoM)', digits=(16, 2), required=True, readonly=True, states={'draft': [('readonly', False)]}),
860 'product_uom': fields.many2one('product.uom', 'Unit of Measure ', required=True, readonly=True, states={'draft': [('readonly', False)]}),
861 'product_uos_qty': fields.float('Quantity (UoS)', readonly=True, states={'draft': [('readonly', False)]}),
862 'product_uos': fields.many2one('product.uom', 'Product UoS'),
863 'product_packaging': fields.many2one('product.packaging', 'Packaging'),
864 'move_ids': fields.one2many('stock.move', 'sale_line_id', 'Inventory Moves', readonly=True),
865 'discount': fields.float('Discount (%)', digits=(16, 2), readonly=True, states={'draft': [('readonly', False)]}),
866 'number_packages': fields.function(_number_packages, method=True, type='integer', string='Number Packages'),
867 'notes': fields.text('Notes'),
868 'th_weight': fields.float('Weight', readonly=True, states={'draft': [('readonly', False)]}),
869 'state': fields.selection([('draft', 'Draft'),('confirmed', 'Confirmed'),('done', 'Done'),('cancel', 'Cancelled'),('exception', 'Exception')], 'State', required=True, readonly=True,
870 help='* The \'Draft\' state is set when the related sales order in draft state. \
871 \n* The \'Confirmed\' state is set when the related sales order is confirmed. \
872 \n* The \'Exception\' state is set when the related sales order is set as exception. \
873 \n* The \'Done\' state is set when the sales order line has been picked. \
874 \n* The \'Cancelled\' state is set when a user cancel the sales order related.'),
875 'order_partner_id': fields.related('order_id', 'partner_id', type='many2one', relation='res.partner', store=True, string='Customer'),
876 'salesman_id':fields.related('order_id', 'user_id', type='many2one', relation='res.users', store=True, string='Salesman'),
877 'company_id': fields.related('order_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True),
879 _order = 'sequence, id desc'
883 'product_uom_qty': 1,
884 'product_uos_qty': 1,
888 'type': 'make_to_stock',
889 'product_packaging': False,
893 def invoice_line_create(self, cr, uid, ids, context=None):
897 def _get_line_qty(line):
898 if (line.order_id.invoice_quantity=='order') or not line.procurement_id:
900 return line.product_uos_qty or 0.0
901 return line.product_uom_qty
903 return self.pool.get('procurement.order').quantity_get(cr, uid,
904 line.procurement_id.id, context=context)
906 def _get_line_uom(line):
907 if (line.order_id.invoice_quantity=='order') or not line.procurement_id:
909 return line.product_uos.id
910 return line.product_uom.id
912 return self.pool.get('procurement.order').uom_get(cr, uid,
913 line.procurement_id.id, context=context)
917 for line in self.browse(cr, uid, ids, context=context):
918 if not line.invoiced:
920 a = line.product_id.product_tmpl_id.property_account_income.id
922 a = line.product_id.categ_id.property_account_income_categ.id
924 raise osv.except_osv(_('Error !'),
925 _('There is no income account defined ' \
926 'for this product: "%s" (id:%d)') % \
927 (line.product_id.name, line.product_id.id,))
929 prop = self.pool.get('ir.property').get(cr, uid,
930 'property_account_income_categ', 'product.category',
932 a = prop and prop.id or False
933 uosqty = _get_line_qty(line)
934 uos_id = _get_line_uom(line)
937 pu = round(line.price_unit * line.product_uom_qty / uosqty,
938 self.pool.get('decimal.precision').precision_get(cr, uid, 'Sale Price'))
939 fpos = line.order_id.fiscal_position or False
940 a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
942 raise osv.except_osv(_('Error !'),
943 _('There is no income category account defined in default Properties for Product Category or Fiscal Position is not defined !'))
944 inv_id = self.pool.get('account.invoice.line').create(cr, uid, {
946 'origin': line.order_id.name,
950 'discount': line.discount,
952 'product_id': line.product_id.id or False,
953 'invoice_line_tax_id': [(6, 0, [x.id for x in line.tax_id])],
955 'account_analytic_id': line.order_id.project_id and line.order_id.project_id.id or False,
957 cr.execute('insert into sale_order_line_invoice_rel (order_line_id,invoice_id) values (%s,%s)', (line.id, inv_id))
958 self.write(cr, uid, [line.id], {'invoiced': True})
959 sales[line.order_id.id] = True
960 create_ids.append(inv_id)
961 # Trigger workflow events
962 wf_service = netsvc.LocalService("workflow")
963 for sid in sales.keys():
964 wf_service.trg_write(uid, 'sale.order', sid, cr)
967 def button_cancel(self, cr, uid, ids, context=None):
968 for line in self.browse(cr, uid, ids, context=context):
970 raise osv.except_osv(_('Invalid action !'), _('You cannot cancel a sales order line that has already been invoiced !'))
971 for move_line in line.move_ids:
972 if move_line.state != 'cancel':
973 raise osv.except_osv(
974 _('Could not cancel sales order line!'),
975 _('You must first cancel stock moves attached to this sales order line.'))
976 return self.write(cr, uid, ids, {'state': 'cancel'})
978 def button_confirm(self, cr, uid, ids, context=None):
979 return self.write(cr, uid, ids, {'state': 'confirmed'})
981 def button_done(self, cr, uid, ids, context=None):
982 wf_service = netsvc.LocalService("workflow")
983 res = self.write(cr, uid, ids, {'state': 'done'})
984 for line in self.browse(cr, uid, ids, context=context):
985 wf_service.trg_write(uid, 'sale.order', line.order_id.id, cr)
988 def uos_change(self, cr, uid, ids, product_uos, product_uos_qty=0, product_id=None):
989 product_obj = self.pool.get('product.product')
991 return {'value': {'product_uom': product_uos,
992 'product_uom_qty': product_uos_qty}, 'domain': {}}
994 product = product_obj.browse(cr, uid, product_id)
996 'product_uom': product.uom_id.id,
998 # FIXME must depend on uos/uom of the product and not only of the coeff.
1001 'product_uom_qty': product_uos_qty / product.uos_coeff,
1002 'th_weight': product_uos_qty / product.uos_coeff * product.weight
1004 except ZeroDivisionError:
1006 return {'value': value}
1008 def copy_data(self, cr, uid, id, default=None, context=None):
1011 default.update({'state': 'draft', 'move_ids': [], 'invoiced': False, 'invoice_lines': []})
1012 return super(sale_order_line, self).copy_data(cr, uid, id, default, context=context)
1014 def product_id_change(self, cr, uid, ids, pricelist, product, qty=0,
1015 uom=False, qty_uos=0, uos=False, name='', partner_id=False,
1016 lang=False, update_tax=True, date_order=False, packaging=False, fiscal_position=False, flag=False):
1018 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.'))
1020 product_uom_obj = self.pool.get('product.uom')
1021 partner_obj = self.pool.get('res.partner')
1022 product_obj = self.pool.get('product.product')
1024 lang = partner_obj.browse(cr, uid, partner_id).lang
1025 context = {'lang': lang, 'partner_id': partner_id}
1028 return {'value': {'th_weight': 0, 'product_packaging': False,
1029 'product_uos_qty': qty}, 'domain': {'product_uom': [],
1032 date_order = time.strftime('%Y-%m-%d')
1035 product_obj = product_obj.browse(cr, uid, product, context=context)
1036 if not packaging and product_obj.packaging:
1037 packaging = product_obj.packaging[0].id
1038 result['product_packaging'] = packaging
1041 default_uom = product_obj.uom_id and product_obj.uom_id.id
1042 pack = self.pool.get('product.packaging').browse(cr, uid, packaging, context=context)
1043 q = product_uom_obj._compute_qty(cr, uid, uom, pack.qty, default_uom)
1044 # qty = qty - qty % q + q
1045 if qty and (q and not (qty % q) == 0):
1046 ean = pack.ean or _('(n/a)')
1049 warn_msg = _("You selected a quantity of %d Units.\n"
1050 "But it's not compatible with the selected packaging.\n"
1051 "Here is a proposition of quantities according to the packaging:\n\n"
1052 "EAN: %s Quantity: %s Type of ul: %s") % \
1053 (qty, ean, qty_pack, type_ul.name)
1055 'title': _('Picking Information !'),
1058 result['product_uom_qty'] = qty
1062 uom2 = product_uom_obj.browse(cr, uid, uom)
1063 if product_obj.uom_id.category_id.id != uom2.category_id.id:
1066 if product_obj.uos_id:
1067 uos2 = product_uom_obj.browse(cr, uid, uos)
1068 if product_obj.uos_id.category_id.id != uos2.category_id.id:
1072 if product_obj.description_sale:
1073 result['notes'] = product_obj.description_sale
1074 fpos = fiscal_position and self.pool.get('account.fiscal.position').browse(cr, uid, fiscal_position) or False
1075 if update_tax: #The quantity only have changed
1076 result['delay'] = (product_obj.sale_delay or 0.0)
1077 result['tax_id'] = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, product_obj.taxes_id)
1078 result.update({'type': product_obj.procure_method})
1081 result['name'] = self.pool.get('product.product').name_get(cr, uid, [product_obj.id], context=context)[0][1]
1083 if (not uom) and (not uos):
1084 result['product_uom'] = product_obj.uom_id.id
1085 if product_obj.uos_id:
1086 result['product_uos'] = product_obj.uos_id.id
1087 result['product_uos_qty'] = qty * product_obj.uos_coeff
1088 uos_category_id = product_obj.uos_id.category_id.id
1090 result['product_uos'] = False
1091 result['product_uos_qty'] = qty
1092 uos_category_id = False
1093 result['th_weight'] = qty * product_obj.weight
1094 domain = {'product_uom':
1095 [('category_id', '=', product_obj.uom_id.category_id.id)],
1097 [('category_id', '=', uos_category_id)]}
1099 elif uos and not uom: # only happens if uom is False
1100 result['product_uom'] = product_obj.uom_id and product_obj.uom_id.id
1101 result['product_uom_qty'] = qty_uos / product_obj.uos_coeff
1102 result['th_weight'] = result['product_uom_qty'] * product_obj.weight
1103 elif uom: # whether uos is set or not
1104 default_uom = product_obj.uom_id and product_obj.uom_id.id
1105 q = product_uom_obj._compute_qty(cr, uid, uom, qty, default_uom)
1106 if product_obj.uos_id:
1107 result['product_uos'] = product_obj.uos_id.id
1108 result['product_uos_qty'] = qty * product_obj.uos_coeff
1110 result['product_uos'] = False
1111 result['product_uos_qty'] = qty
1112 result['th_weight'] = q * product_obj.weight # Round the quantity up
1115 uom2 = product_obj.uom_id
1116 if (product_obj.type=='product') and (product_obj.virtual_available * uom2.factor < qty * product_obj.uom_id.factor) \
1117 and (product_obj.procure_method=='make_to_stock'):
1119 'title': _('Not enough stock !'),
1120 'message': _('You plan to sell %.2f %s but you only have %.2f %s available !\nThe real stock is %.2f %s. (without reservations)') %
1121 (qty, uom2 and uom2.name or product_obj.uom_id.name,
1122 max(0,product_obj.virtual_available), product_obj.uom_id.name,
1123 max(0,product_obj.qty_available), product_obj.uom_id.name)
1128 'title': 'No Pricelist !',
1130 'You have to select a pricelist or a customer in the sales form !\n'
1131 'Please set one before choosing a product.'
1134 price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist],
1135 product, qty or 1.0, partner_id, {
1141 'title': 'No valid pricelist line found !',
1143 "Couldn't find a pricelist line matching this product and quantity.\n"
1144 "You have to change either the product, the quantity or the pricelist."
1147 result.update({'price_unit': price})
1148 return {'value': result, 'domain': domain, 'warning': warning}
1150 def product_uom_change(self, cursor, user, ids, pricelist, product, qty=0,
1151 uom=False, qty_uos=0, uos=False, name='', partner_id=False,
1152 lang=False, update_tax=True, date_order=False):
1153 res = self.product_id_change(cursor, user, ids, pricelist, product,
1154 qty=qty, uom=uom, qty_uos=qty_uos, uos=uos, name=name,
1155 partner_id=partner_id, lang=lang, update_tax=update_tax,
1156 date_order=date_order)
1157 if 'product_uom' in res['value']:
1158 del res['value']['product_uom']
1160 res['value']['price_unit'] = 0.0
1163 def unlink(self, cr, uid, ids, context=None):
1166 """Allows to delete sales order lines in draft,cancel states"""
1167 for rec in self.browse(cr, uid, ids, context=context):
1168 if rec.state not in ['draft', 'cancel']:
1169 raise osv.except_osv(_('Invalid action !'), _('Cannot delete a sales order line which is %s !') %(rec.state,))
1170 return super(sale_order_line, self).unlink(cr, uid, ids, context=context)
1174 class sale_config_picking_policy(osv.osv_memory):
1175 _name = 'sale.config.picking_policy'
1176 _inherit = 'res.config'
1179 'name': fields.char('Name', size=64),
1180 'picking_policy': fields.selection([
1181 ('direct', 'Direct Delivery'),
1182 ('one', 'All at Once')
1183 ], '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.."),
1184 'order_policy': fields.selection([
1185 ('manual', 'Invoice Based on Sales Orders'),
1186 ('picking', 'Invoice Based on Deliveries'),
1187 ], 'Shipping Default Policy', required=True,
1188 help="You can generate invoices based on sales orders or based on shippings."),
1189 'step': fields.selection([
1190 ('one', 'Delivery Order Only'),
1191 ('two', 'Picking List & Delivery Order')
1192 ], 'Steps To Deliver a Sales Order', required=True,
1193 help="By default, OpenERP is able to manage complex routing and paths "\
1194 "of products in your warehouse and partner locations. This will configure "\
1195 "the most common and simple methods to deliver products to the customer "\
1196 "in one or two operations by the worker.")
1199 'picking_policy': 'direct',
1200 'order_policy': 'manual',
1204 def execute(self, cr, uid, ids, context=None):
1205 for o in self.browse(cr, uid, ids, context=context):
1206 ir_values_obj = self.pool.get('ir.values')
1207 ir_values_obj.set(cr, uid, 'default', False, 'picking_policy', ['sale.order'], o.picking_policy)
1208 ir_values_obj.set(cr, uid, 'default', False, 'order_policy', ['sale.order'], o.order_policy)
1210 md = self.pool.get('ir.model.data')
1211 location_id = md.get_object_reference(cr, uid, 'stock', 'stock_location_output')
1212 location_id = location_id and location_id[1] or False
1213 self.pool.get('stock.location').write(cr, uid, [location_id], {'chained_auto_packing': 'manual'})
1215 sale_config_picking_policy()
1217 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: