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, m.state as state, p.type as tp
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 m.state, mp.state, p.sale_id, p.type''', (tuple(ids),))
108 for oid, nbr, state, move_state, type_pick in cr.fetchall():
109 if state == 'cancel':
111 res[oid][1] += nbr or 0.0
112 if state == 'done' or move_state == 'done':
113 res[oid][0] += nbr or 0.0
115 if type_pick == 'in':#this is a returned picking
116 res[oid][1] -= 2*nbr or 0.0 # Deducting the return picking qty
117 if state == 'done' or move_state == 'done':
119 res[oid][0] -= nbr or 0.0
125 res[r] = 100.0 * res[r][0] / res[r][1]
126 for order in self.browse(cr, uid, ids, context=context):
128 res[order.id] = 100.0
131 def _invoiced_rate(self, cursor, user, ids, name, arg, context=None):
133 for sale in self.browse(cursor, user, ids, context=context):
138 for invoice in sale.invoice_ids:
139 if invoice.state not in ('draft', 'cancel'):
140 tot += invoice.amount_untaxed
142 res[sale.id] = min(100.0, tot * 100.0 / (sale.amount_untaxed or 1.00))
147 def _invoiced(self, cursor, user, ids, name, arg, context=None):
149 for sale in self.browse(cursor, user, ids, context=context):
151 for invoice in sale.invoice_ids:
152 if invoice.state != 'paid':
155 if not sale.invoice_ids:
159 def _invoiced_search(self, cursor, user, obj, name, args, context=None):
168 clause += 'AND inv.state = \'paid\''
170 clause += 'AND inv.state != \'cancel\' AND sale.state != \'cancel\' AND inv.state <> \'paid\' AND rel.order_id = sale.id '
171 sale_clause = ', sale_order AS sale '
174 cursor.execute('SELECT rel.order_id ' \
175 'FROM sale_order_invoice_rel AS rel, account_invoice AS inv '+ sale_clause + \
176 'WHERE rel.invoice_id = inv.id ' + clause)
177 res = cursor.fetchall()
179 cursor.execute('SELECT sale.id ' \
180 'FROM sale_order AS sale ' \
181 'WHERE sale.id NOT IN ' \
182 '(SELECT rel.order_id ' \
183 'FROM sale_order_invoice_rel AS rel) and sale.state != \'cancel\'')
184 res.extend(cursor.fetchall())
186 return [('id', '=', 0)]
187 return [('id', 'in', [x[0] for x in res])]
189 def _get_order(self, cr, uid, ids, context=None):
191 for line in self.pool.get('sale.order.line').browse(cr, uid, ids, context=context):
192 result[line.order_id.id] = True
196 'name': fields.char('Order Reference', size=64, required=True,
197 readonly=True, states={'draft': [('readonly', False)]}, select=True),
198 'shop_id': fields.many2one('sale.shop', 'Shop', required=True, readonly=True, states={'draft': [('readonly', False)]}),
199 'origin': fields.char('Source Document', size=64, help="Reference of the document that generated this sales order request."),
200 'client_order_ref': fields.char('Customer Reference', size=64),
201 'state': fields.selection([
202 ('draft', 'Quotation'),
203 ('waiting_date', 'Waiting Schedule'),
204 ('manual', 'Manual In Progress'),
205 ('progress', 'In Progress'),
206 ('shipping_except', 'Shipping Exception'),
207 ('invoice_except', 'Invoice Exception'),
209 ('cancel', 'Cancelled')
210 ], '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),
211 'date_order': fields.date('Ordered Date', required=True, readonly=True, select=True, states={'draft': [('readonly', False)]}),
212 'create_date': fields.date('Creation Date', readonly=True, select=True, help="Date on which sales order is created."),
213 'date_confirm': fields.date('Confirmation Date', readonly=True, select=True, help="Date on which sales order is confirmed."),
214 'user_id': fields.many2one('res.users', 'Salesman', states={'draft': [('readonly', False)]}, select=True),
215 'partner_id': fields.many2one('res.partner', 'Customer', readonly=True, states={'draft': [('readonly', False)]}, required=True, change_default=True, select=True),
216 '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."),
217 '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."),
218 '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."),
220 '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."),
221 'picking_policy': fields.selection([('direct', 'Partial Delivery'), ('one', 'Complete Delivery')],
222 '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?"""),
223 'order_policy': fields.selection([
224 ('prepaid', 'Payment Before Delivery'),
225 ('manual', 'Shipping & Manual Invoice'),
226 ('postpaid', 'Invoice On Order After Delivery'),
227 ('picking', 'Invoice From The Picking'),
228 ], 'Shipping Policy', required=True, readonly=True, states={'draft': [('readonly', False)]},
229 help="""The Shipping Policy is used to synchronise invoice and delivery operations.
230 - The 'Pay Before delivery' choice will first generate the invoice and then generate the picking order after the payment of this invoice.
231 - 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.
232 - The 'Invoice On Order After Delivery' choice will generate the draft invoice based on sales order after all picking lists have been finished.
233 - The 'Invoice From The Picking' choice is used to create an invoice during the picking process."""),
234 'pricelist_id': fields.many2one('product.pricelist', 'Pricelist', required=True, readonly=True, states={'draft': [('readonly', False)]}, help="Pricelist for current sales order."),
235 'project_id': fields.many2one('account.analytic.account', 'Analytic Account', readonly=True, states={'draft': [('readonly', False)]}, help="The analytic account related to a sales order."),
237 'order_line': fields.one2many('sale.order.line', 'order_id', 'Order Lines', readonly=True, states={'draft': [('readonly', False)]}),
238 '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)."),
239 '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."),
240 '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."),
241 'picked_rate': fields.function(_picked_rate, method=True, string='Picked', type='float'),
242 'invoiced_rate': fields.function(_invoiced_rate, method=True, string='Invoiced', type='float'),
243 'invoiced': fields.function(_invoiced, method=True, string='Paid',
244 fnct_search=_invoiced_search, type='boolean', help="It indicates that an invoice has been paid."),
245 'note': fields.text('Notes'),
247 'amount_untaxed': fields.function(_amount_all, method=True, digits_compute= dp.get_precision('Sale Price'), string='Untaxed Amount',
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 amount without tax."),
253 'amount_tax': fields.function(_amount_all, method=True, digits_compute= dp.get_precision('Sale Price'), string='Taxes',
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 tax amount."),
259 'amount_total': fields.function(_amount_all, method=True, digits_compute= dp.get_precision('Sale Price'), string='Total',
261 'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line'], 10),
262 'sale.order.line': (_get_order, ['price_unit', 'tax_id', 'discount', 'product_uom_qty'], 10),
264 multi='sums', help="The total amount."),
266 '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)]}),
267 'payment_term': fields.many2one('account.payment.term', 'Payment Term'),
268 'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position'),
269 'company_id': fields.related('shop_id','company_id',type='many2one',relation='res.company',string='Company',store=True,readonly=True)
272 'picking_policy': 'direct',
273 'date_order': lambda *a: time.strftime('%Y-%m-%d'),
274 'order_policy': 'manual',
276 'user_id': lambda obj, cr, uid, context: uid,
277 'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'sale.order'),
278 'invoice_quantity': 'order',
279 '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'],
280 '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'],
281 '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'],
284 ('name_uniq', 'unique(name)', 'Order Reference must be unique !'),
289 def unlink(self, cr, uid, ids, context=None):
290 sale_orders = self.read(cr, uid, ids, ['state'], context=context)
292 for s in sale_orders:
293 if s['state'] in ['draft', 'cancel']:
294 unlink_ids.append(s['id'])
296 raise osv.except_osv(_('Invalid action !'), _('Cannot delete Sales Order(s) which are already confirmed !'))
297 return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
299 def onchange_shop_id(self, cr, uid, ids, shop_id):
302 shop = self.pool.get('sale.shop').browse(cr, uid, shop_id)
303 v['project_id'] = shop.project_id.id
304 # Que faire si le client a une pricelist a lui ?
305 if shop.pricelist_id.id:
306 v['pricelist_id'] = shop.pricelist_id.id
309 def action_cancel_draft(self, cr, uid, ids, *args):
312 cr.execute('select id from sale_order_line where order_id IN %s and state=%s', (tuple(ids), 'cancel'))
313 line_ids = map(lambda x: x[0], cr.fetchall())
314 self.write(cr, uid, ids, {'state': 'draft', 'invoice_ids': [], 'shipped': 0})
315 self.pool.get('sale.order.line').write(cr, uid, line_ids, {'invoiced': False, 'state': 'draft', 'invoice_lines': [(6, 0, [])]})
316 wf_service = netsvc.LocalService("workflow")
318 # Deleting the existing instance of workflow for SO
319 wf_service.trg_delete(uid, 'sale.order', inv_id, cr)
320 wf_service.trg_create(uid, 'sale.order', inv_id, cr)
321 for (id,name) in self.name_get(cr, uid, ids):
322 message = _("The sales order '%s' has been set in draft state.") %(name,)
323 self.log(cr, uid, id, message)
326 def onchange_partner_id(self, cr, uid, ids, part):
328 return {'value': {'partner_invoice_id': False, 'partner_shipping_id': False, 'partner_order_id': False, 'payment_term': False, 'fiscal_position': False}}
330 addr = self.pool.get('res.partner').address_get(cr, uid, [part], ['delivery', 'invoice', 'contact'])
331 part = self.pool.get('res.partner').browse(cr, uid, part)
332 pricelist = part.property_product_pricelist and part.property_product_pricelist.id or False
333 payment_term = part.property_payment_term and part.property_payment_term.id or False
334 fiscal_position = part.property_account_position and part.property_account_position.id or False
335 dedicated_salesman = part.user_id and part.user_id.id or uid
337 'partner_invoice_id': addr['invoice'],
338 'partner_order_id': addr['contact'],
339 'partner_shipping_id': addr['delivery'],
340 'payment_term': payment_term,
341 'fiscal_position': fiscal_position,
342 'user_id': dedicated_salesman,
345 val['pricelist_id'] = pricelist
346 return {'value': val}
348 def shipping_policy_change(self, cr, uid, ids, policy, context=None):
352 if policy == 'prepaid':
354 elif policy == 'picking':
355 inv_qty = 'procurement'
356 return {'value': {'invoice_quantity': inv_qty}}
358 def write(self, cr, uid, ids, vals, context=None):
359 if vals.get('order_policy', False):
360 if vals['order_policy'] == 'prepaid':
361 vals.update({'invoice_quantity': 'order'})
362 elif vals['order_policy'] == 'picking':
363 vals.update({'invoice_quantity': 'procurement'})
364 return super(sale_order, self).write(cr, uid, ids, vals, context=context)
366 def create(self, cr, uid, vals, context=None):
367 if vals.get('order_policy', False):
368 if vals['order_policy'] == 'prepaid':
369 vals.update({'invoice_quantity': 'order'})
370 if vals['order_policy'] == 'picking':
371 vals.update({'invoice_quantity': 'procurement'})
372 return super(sale_order, self).create(cr, uid, vals, context=context)
374 def button_dummy(self, cr, uid, ids, context=None):
377 #FIXME: the method should return the list of invoices created (invoice_ids)
378 # and not the id of the last invoice created (res). The problem is that we
379 # cannot change it directly since the method is called by the sales order
380 # workflow and I suppose it expects a single id...
381 def _inv_get(self, cr, uid, order, context=None):
384 def _make_invoice(self, cr, uid, order, lines, context=None):
385 journal_obj = self.pool.get('account.journal')
386 inv_obj = self.pool.get('account.invoice')
387 obj_invoice_line = self.pool.get('account.invoice.line')
391 journal_ids = journal_obj.search(cr, uid, [('type', '=', 'sale'), ('company_id', '=', order.company_id.id)], limit=1)
393 raise osv.except_osv(_('Error !'),
394 _('There is no sales journal defined for this company: "%s" (id:%d)') % (order.company_id.name, order.company_id.id))
395 a = order.partner_id.property_account_receivable.id
396 pay_term = order.payment_term and order.payment_term.id or False
397 invoiced_sale_line_ids = self.pool.get('sale.order.line').search(cr, uid, [('order_id', '=', order.id), ('invoiced', '=', True)], context=context)
398 from_line_invoice_ids = []
399 for invoiced_sale_line_id in self.pool.get('sale.order.line').browse(cr, uid, invoiced_sale_line_ids, context=context):
400 for invoice_line_id in invoiced_sale_line_id.invoice_lines:
401 if invoice_line_id.invoice_id.id not in from_line_invoice_ids:
402 from_line_invoice_ids.append(invoice_line_id.invoice_id.id)
403 for preinv in order.invoice_ids:
404 if preinv.state not in ('cancel',) and preinv.id not in from_line_invoice_ids:
405 for preline in preinv.invoice_line:
406 inv_line_id = obj_invoice_line.copy(cr, uid, preline.id, {'invoice_id': False, 'price_unit': -preline.price_unit})
407 lines.append(inv_line_id)
409 'name': order.client_order_ref or '',
410 'origin': order.name,
411 'type': 'out_invoice',
412 'reference': order.client_order_ref or order.name,
414 'partner_id': order.partner_id.id,
415 'journal_id': journal_ids[0],
416 'address_invoice_id': order.partner_invoice_id.id,
417 'address_contact_id': order.partner_order_id.id,
418 'invoice_line': [(6, 0, lines)],
419 'currency_id': order.pricelist_id.currency_id.id,
420 'comment': order.note,
421 'payment_term': pay_term,
422 'fiscal_position': order.fiscal_position.id or order.partner_id.property_account_position.id,
423 'date_invoice': context.get('date_invoice',False),
424 'company_id': order.company_id.id,
425 'user_id': order.user_id and order.user_id.id or False
427 inv.update(self._inv_get(cr, uid, order))
428 inv_id = inv_obj.create(cr, uid, inv, context=context)
429 data = inv_obj.onchange_payment_term_date_invoice(cr, uid, [inv_id], pay_term, time.strftime('%Y-%m-%d'))
430 if data.get('value', False):
431 inv_obj.write(cr, uid, [inv_id], data['value'], context=context)
432 inv_obj.button_compute(cr, uid, [inv_id])
435 def manual_invoice(self, cr, uid, ids, context=None):
436 mod_obj = self.pool.get('ir.model.data')
437 wf_service = netsvc.LocalService("workflow")
441 for record in self.pool.get('sale.order').browse(cr, uid, id).invoice_ids:
442 inv_ids.add(record.id)
443 # inv_ids would have old invoices if any
445 wf_service.trg_validate(uid, 'sale.order', id, 'manual_invoice', cr)
446 for record in self.pool.get('sale.order').browse(cr, uid, id).invoice_ids:
447 inv_ids1.add(record.id)
448 inv_ids = list(inv_ids1.difference(inv_ids))
450 res = mod_obj.get_object_reference(cr, uid, 'account', 'invoice_form')
451 res_id = res and res[1] or False,
454 'name': 'Customer Invoices',
458 'res_model': 'account.invoice',
459 'context': "{'type':'out_invoice'}",
460 'type': 'ir.actions.act_window',
463 'res_id': inv_ids and inv_ids[0] or False,
466 def action_invoice_create(self, cr, uid, ids, grouped=False, states=['confirmed', 'done', 'exception'], date_inv = False, context=None):
470 picking_obj = self.pool.get('stock.picking')
471 invoice = self.pool.get('account.invoice')
472 obj_sale_order_line = self.pool.get('sale.order.line')
475 # If date was specified, use it as date invoiced, usefull when invoices are generated this month and put the
476 # last day of the last month as invoice date
478 context['date_inv'] = date_inv
479 for o in self.browse(cr, uid, ids, context=context):
481 for line in o.order_line:
484 elif (line.state in states):
485 lines.append(line.id)
486 created_lines = obj_sale_order_line.invoice_line_create(cr, uid, lines)
488 invoices.setdefault(o.partner_id.id, []).append((o, created_lines))
490 for o in self.browse(cr, uid, ids, context=context):
491 for i in o.invoice_ids:
492 if i.state == 'draft':
494 for val in invoices.values():
496 res = self._make_invoice(cr, uid, val[0][0], reduce(lambda x, y: x + y, [l for o, l in val], []), context=context)
499 invoice_ref += o.name + '|'
500 self.write(cr, uid, [o.id], {'state': 'progress'})
501 if o.order_policy == 'picking':
502 picking_obj.write(cr, uid, map(lambda x: x.id, o.picking_ids), {'invoice_state': 'invoiced'})
503 cr.execute('insert into sale_order_invoice_rel (order_id,invoice_id) values (%s,%s)', (o.id, res))
504 invoice.write(cr, uid, [res], {'origin': invoice_ref, 'name': invoice_ref})
506 for order, il in val:
507 res = self._make_invoice(cr, uid, order, il, context=context)
508 invoice_ids.append(res)
509 self.write(cr, uid, [order.id], {'state': 'progress'})
510 if order.order_policy == 'picking':
511 picking_obj.write(cr, uid, map(lambda x: x.id, order.picking_ids), {'invoice_state': 'invoiced'})
512 cr.execute('insert into sale_order_invoice_rel (order_id,invoice_id) values (%s,%s)', (order.id, res))
515 def action_invoice_cancel(self, cr, uid, ids, context=None):
518 for sale in self.browse(cr, uid, ids, context=context):
519 for line in sale.order_line:
521 # Check if the line is invoiced (has asociated invoice
522 # lines from non-cancelled invoices).
525 for iline in line.invoice_lines:
526 if iline.invoice_id and iline.invoice_id.state != 'cancel':
529 # Update the line (only when needed)
530 if line.invoiced != invoiced:
531 self.pool.get('sale.order.line').write(cr, uid, [line.id], {'invoiced': invoiced}, context=context)
532 self.write(cr, uid, ids, {'state': 'invoice_except', 'invoice_ids': False}, context=context)
535 def action_invoice_end(self, cr, uid, ids, context=None):
536 for order in self.browse(cr, uid, ids, context=context):
538 # Update the sale order lines state (and invoiced flag).
540 for line in order.order_line:
543 # Check if the line is invoiced (has asociated invoice
544 # lines from non-cancelled invoices).
547 for iline in line.invoice_lines:
548 if iline.invoice_id and iline.invoice_id.state != 'cancel':
551 if line.invoiced != invoiced:
552 vals['invoiced'] = invoiced
553 # If the line was in exception state, now it gets confirmed.
554 if line.state == 'exception':
555 vals['state'] = 'confirmed'
556 # Update the line (only when needed).
558 self.pool.get('sale.order.line').write(cr, uid, [line.id], vals, context=context)
560 # Update the sales order state.
562 if order.state == 'invoice_except':
563 self.write(cr, uid, [order.id], {'state': 'progress'}, context=context)
566 def action_cancel(self, cr, uid, ids, context=None):
567 wf_service = netsvc.LocalService("workflow")
570 sale_order_line_obj = self.pool.get('sale.order.line')
571 proc_obj = self.pool.get('procurement.order')
572 for sale in self.browse(cr, uid, ids, context=context):
573 for pick in sale.picking_ids:
574 if pick.state not in ('draft', 'cancel'):
575 raise osv.except_osv(
576 _('Could not cancel sales order !'),
577 _('You must first cancel all picking attached to this sales order.'))
578 if pick.state == 'cancel':
579 for mov in pick.move_lines:
580 proc_ids = proc_obj.search(cr, uid, [('move_id', '=', mov.id)])
582 for proc in proc_ids:
583 wf_service.trg_validate(uid, 'procurement.order', proc, 'button_check', cr)
584 for r in self.read(cr, uid, ids, ['picking_ids']):
585 for pick in r['picking_ids']:
586 wf_service.trg_validate(uid, 'stock.picking', pick, 'button_cancel', cr)
587 for inv in sale.invoice_ids:
588 if inv.state not in ('draft', 'cancel'):
589 raise osv.except_osv(
590 _('Could not cancel this sales order !'),
591 _('You must first cancel all invoices attached to this sales order.'))
592 for r in self.read(cr, uid, ids, ['invoice_ids']):
593 for inv in r['invoice_ids']:
594 wf_service.trg_validate(uid, 'account.invoice', inv, 'invoice_cancel', cr)
595 sale_order_line_obj.write(cr, uid, [l.id for l in sale.order_line],
597 message = _("The sales order '%s' has been cancelled.") % (sale.name,)
598 self.log(cr, uid, sale.id, message)
599 self.write(cr, uid, ids, {'state': 'cancel'})
602 def action_wait(self, cr, uid, ids, *args):
603 for o in self.browse(cr, uid, ids):
604 if (o.order_policy == 'manual'):
605 self.write(cr, uid, [o.id], {'state': 'manual', 'date_confirm': time.strftime('%Y-%m-%d')})
607 self.write(cr, uid, [o.id], {'state': 'progress', 'date_confirm': time.strftime('%Y-%m-%d')})
608 self.pool.get('sale.order.line').button_confirm(cr, uid, [x.id for x in o.order_line])
609 message = _("The quotation '%s' has been converted to a sales order.") % (o.name,)
610 self.log(cr, uid, o.id, message)
613 def procurement_lines_get(self, cr, uid, ids, *args):
615 for order in self.browse(cr, uid, ids, context={}):
616 for line in order.order_line:
617 if line.procurement_id:
618 res.append(line.procurement_id.id)
621 # if mode == 'finished':
622 # returns True if all lines are done, False otherwise
623 # if mode == 'canceled':
624 # returns True if there is at least one canceled line, False otherwise
625 def test_state(self, cr, uid, ids, mode, *args):
626 assert mode in ('finished', 'canceled'), _("invalid mode for test_state")
631 write_cancel_ids = []
632 for order in self.browse(cr, uid, ids, context={}):
633 for line in order.order_line:
634 if (not line.procurement_id) or (line.procurement_id.state=='done'):
635 if line.state != 'done':
636 write_done_ids.append(line.id)
639 if line.procurement_id:
640 if (line.procurement_id.state == 'cancel'):
642 if line.state != 'exception':
643 write_cancel_ids.append(line.id)
647 self.pool.get('sale.order.line').write(cr, uid, write_done_ids, {'state': 'done'})
649 self.pool.get('sale.order.line').write(cr, uid, write_cancel_ids, {'state': 'exception'})
651 if mode == 'finished':
653 elif mode == 'canceled':
659 def action_ship_create(self, cr, uid, ids, *args):
660 wf_service = netsvc.LocalService("workflow")
662 move_obj = self.pool.get('stock.move')
663 proc_obj = self.pool.get('procurement.order')
664 company = self.pool.get('res.users').browse(cr, uid, uid).company_id
665 for order in self.browse(cr, uid, ids, context={}):
667 output_id = order.shop_id.warehouse_id.lot_output_id.id
669 for line in order.order_line:
671 date_planned = datetime.now() + relativedelta(days=line.delay or 0.0)
672 date_planned = (date_planned - timedelta(days=company.security_lead)).strftime('%Y-%m-%d %H:%M:%S')
674 if line.state == 'done':
677 if line.product_id and line.product_id.product_tmpl_id.type in ('product', 'consu'):
678 location_id = order.shop_id.warehouse_id.lot_stock_id.id
680 pick_name = self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.out')
681 picking_id = self.pool.get('stock.picking').create(cr, uid, {
683 'origin': order.name,
686 'move_type': order.picking_policy,
688 'address_id': order.partner_shipping_id.id,
690 'invoice_state': (order.order_policy=='picking' and '2binvoiced') or 'none',
691 'company_id': order.company_id.id,
693 move_id = self.pool.get('stock.move').create(cr, uid, {
694 'name': line.name[:64],
695 'picking_id': picking_id,
696 'product_id': line.product_id.id,
697 'date': date_planned,
698 'date_expected': date_planned,
699 'product_qty': line.product_uom_qty,
700 'product_uom': line.product_uom.id,
701 'product_uos_qty': line.product_uos_qty,
702 'product_uos': (line.product_uos and line.product_uos.id)\
703 or line.product_uom.id,
704 'product_packaging': line.product_packaging.id,
705 'address_id': line.address_allotment_id.id or order.partner_shipping_id.id,
706 'location_id': location_id,
707 'location_dest_id': output_id,
708 'sale_line_id': line.id,
709 'tracking_id': False,
713 'company_id': order.company_id.id,
717 proc_id = self.pool.get('procurement.order').create(cr, uid, {
719 'origin': order.name,
720 'date_planned': date_planned,
721 'product_id': line.product_id.id,
722 'product_qty': line.product_uom_qty,
723 'product_uom': line.product_uom.id,
724 'product_uos_qty': (line.product_uos and line.product_uos_qty)\
725 or line.product_uom_qty,
726 'product_uos': (line.product_uos and line.product_uos.id)\
727 or line.product_uom.id,
728 'location_id': order.shop_id.warehouse_id.lot_stock_id.id,
729 'procure_method': line.type,
731 'property_ids': [(6, 0, [x.id for x in line.property_ids])],
732 'company_id': order.company_id.id,
734 proc_ids.append(proc_id)
735 self.pool.get('sale.order.line').write(cr, uid, [line.id], {'procurement_id': proc_id})
736 if order.state == 'shipping_except':
737 for pick in order.picking_ids:
738 for move in pick.move_lines:
739 if move.state == 'cancel':
740 mov_ids = move_obj.search(cr, uid, [('state', '=', 'cancel'),('sale_line_id', '=', line.id),('picking_id', '=', pick.id)])
742 for mov in move_obj.browse(cr, uid, mov_ids):
743 move_obj.write(cr, uid, [move_id], {'product_qty': mov.product_qty, 'product_uos_qty': mov.product_uos_qty})
744 proc_obj.write(cr, uid, [proc_id], {'product_qty': mov.product_qty, 'product_uos_qty': mov.product_uos_qty})
749 wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
751 for proc_id in proc_ids:
752 wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_confirm', cr)
754 if order.state == 'shipping_except':
755 val['state'] = 'progress'
756 val['shipped'] = False
758 if (order.order_policy == 'manual'):
759 for line in order.order_line:
760 if (not line.invoiced) and (line.state not in ('cancel', 'draft')):
761 val['state'] = 'manual'
763 self.write(cr, uid, [order.id], val)
766 def action_ship_end(self, cr, uid, ids, context=None):
767 for order in self.browse(cr, uid, ids, context=context):
768 val = {'shipped': True}
769 if order.state == 'shipping_except':
770 val['state'] = 'progress'
771 if (order.order_policy == 'manual'):
772 for line in order.order_line:
773 if (not line.invoiced) and (line.state not in ('cancel', 'draft')):
774 val['state'] = 'manual'
776 for line in order.order_line:
778 if line.state == 'exception':
779 towrite.append(line.id)
781 self.pool.get('sale.order.line').write(cr, uid, towrite, {'state': 'done'}, context=context)
782 self.write(cr, uid, [order.id], val)
785 def _log_event(self, cr, uid, ids, factor=0.7, name='Open Order'):
786 invs = self.read(cr, uid, ids, ['date_order', 'partner_id', 'amount_untaxed'])
788 part = inv['partner_id'] and inv['partner_id'][0]
789 pr = inv['amount_untaxed'] or 0.0
790 partnertype = 'customer'
793 'name': 'Order: '+name,
795 'description': 'Order '+str(inv['id']),
798 'date': time.strftime('%Y-%m-%d'),
801 'partner_type': partnertype,
803 'planned_revenue': pr,
807 self.pool.get('res.partner.event').create(cr, uid, event)
809 def has_stockable_products(self, cr, uid, ids, *args):
810 for order in self.browse(cr, uid, ids):
811 for order_line in order.order_line:
812 if order_line.product_id and order_line.product_id.product_tmpl_id.type in ('product', 'consu'):
817 # TODO add a field price_unit_uos
818 # - update it on change product and unit price
819 # - use it in report if there is a uos
820 class sale_order_line(osv.osv):
822 def _amount_line(self, cr, uid, ids, field_name, arg, context=None):
823 tax_obj = self.pool.get('account.tax')
824 cur_obj = self.pool.get('res.currency')
828 for line in self.browse(cr, uid, ids, context=context):
829 price = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
830 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)
831 cur = line.order_id.pricelist_id.currency_id
832 res[line.id] = cur_obj.round(cr, uid, cur, taxes['total'])
835 def _number_packages(self, cr, uid, ids, field_name, arg, context=None):
837 for line in self.browse(cr, uid, ids, context=context):
839 res[line.id] = int((line.product_uom_qty+line.product_packaging.qty-0.0001) / line.product_packaging.qty)
844 _name = 'sale.order.line'
845 _description = 'Sales Order Line'
847 'order_id': fields.many2one('sale.order', 'Order Reference', required=True, ondelete='cascade', select=True, readonly=True, states={'draft':[('readonly',False)]}),
848 'name': fields.char('Description', size=256, required=True, select=True, readonly=True, states={'draft': [('readonly', False)]}),
849 'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of sales order lines."),
850 '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)]}),
851 'product_id': fields.many2one('product.product', 'Product', domain=[('sale_ok', '=', True)], change_default=True),
852 'invoice_lines': fields.many2many('account.invoice.line', 'sale_order_line_invoice_rel', 'order_line_id', 'invoice_id', 'Invoice Lines', readonly=True),
853 'invoiced': fields.boolean('Invoiced', readonly=True),
854 'procurement_id': fields.many2one('procurement.order', 'Procurement'),
855 'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Sale Price'), readonly=True, states={'draft': [('readonly', False)]}),
856 'price_subtotal': fields.function(_amount_line, method=True, string='Subtotal', digits_compute= dp.get_precision('Sale Price')),
857 'tax_id': fields.many2many('account.tax', 'sale_order_tax', 'order_line_id', 'tax_id', 'Taxes', readonly=True, states={'draft': [('readonly', False)]}),
858 'type': fields.selection([('make_to_stock', 'from stock'), ('make_to_order', 'on order')], 'Procurement Method', required=True, readonly=True, states={'draft': [('readonly', False)]}),
859 'property_ids': fields.many2many('mrp.property', 'sale_order_line_property_rel', 'order_id', 'property_id', 'Properties', readonly=True, states={'draft': [('readonly', False)]}),
860 'address_allotment_id': fields.many2one('res.partner.address', 'Allotment Partner'),
861 'product_uom_qty': fields.float('Quantity (UoM)', digits=(16, 2), required=True, readonly=True, states={'draft': [('readonly', False)]}),
862 'product_uom': fields.many2one('product.uom', 'Unit of Measure ', required=True, readonly=True, states={'draft': [('readonly', False)]}),
863 'product_uos_qty': fields.float('Quantity (UoS)', readonly=True, states={'draft': [('readonly', False)]}),
864 'product_uos': fields.many2one('product.uom', 'Product UoS'),
865 'product_packaging': fields.many2one('product.packaging', 'Packaging'),
866 'move_ids': fields.one2many('stock.move', 'sale_line_id', 'Inventory Moves', readonly=True),
867 'discount': fields.float('Discount (%)', digits=(16, 2), readonly=True, states={'draft': [('readonly', False)]}),
868 'number_packages': fields.function(_number_packages, method=True, type='integer', string='Number Packages'),
869 'notes': fields.text('Notes'),
870 'th_weight': fields.float('Weight', readonly=True, states={'draft': [('readonly', False)]}),
871 'state': fields.selection([('draft', 'Draft'),('confirmed', 'Confirmed'),('done', 'Done'),('cancel', 'Cancelled'),('exception', 'Exception')], 'State', required=True, readonly=True,
872 help='* The \'Draft\' state is set when the related sales order in draft state. \
873 \n* The \'Confirmed\' state is set when the related sales order is confirmed. \
874 \n* The \'Exception\' state is set when the related sales order is set as exception. \
875 \n* The \'Done\' state is set when the sales order line has been picked. \
876 \n* The \'Cancelled\' state is set when a user cancel the sales order related.'),
877 'order_partner_id': fields.related('order_id', 'partner_id', type='many2one', relation='res.partner', store=True, string='Customer'),
878 'salesman_id':fields.related('order_id', 'user_id', type='many2one', relation='res.users', store=True, string='Salesman'),
879 'company_id': fields.related('order_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True),
881 _order = 'sequence, id desc'
885 'product_uom_qty': 1,
886 'product_uos_qty': 1,
890 'type': 'make_to_stock',
891 'product_packaging': False,
895 def invoice_line_create(self, cr, uid, ids, context=None):
899 def _get_line_qty(line):
900 if (line.order_id.invoice_quantity=='order') or not line.procurement_id:
902 return line.product_uos_qty or 0.0
903 return line.product_uom_qty
905 return self.pool.get('procurement.order').quantity_get(cr, uid,
906 line.procurement_id.id, context=context)
908 def _get_line_uom(line):
909 if (line.order_id.invoice_quantity=='order') or not line.procurement_id:
911 return line.product_uos.id
912 return line.product_uom.id
914 return self.pool.get('procurement.order').uom_get(cr, uid,
915 line.procurement_id.id, context=context)
919 for line in self.browse(cr, uid, ids, context=context):
920 if not line.invoiced:
922 a = line.product_id.product_tmpl_id.property_account_income.id
924 a = line.product_id.categ_id.property_account_income_categ.id
926 raise osv.except_osv(_('Error !'),
927 _('There is no income account defined ' \
928 'for this product: "%s" (id:%d)') % \
929 (line.product_id.name, line.product_id.id,))
931 prop = self.pool.get('ir.property').get(cr, uid,
932 'property_account_income_categ', 'product.category',
934 a = prop and prop.id or False
935 uosqty = _get_line_qty(line)
936 uos_id = _get_line_uom(line)
939 pu = round(line.price_unit * line.product_uom_qty / uosqty,
940 self.pool.get('decimal.precision').precision_get(cr, uid, 'Sale Price'))
941 fpos = line.order_id.fiscal_position or False
942 a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
944 raise osv.except_osv(_('Error !'),
945 _('There is no income category account defined in default Properties for Product Category or Fiscal Position is not defined !'))
946 inv_id = self.pool.get('account.invoice.line').create(cr, uid, {
948 'origin': line.order_id.name,
952 'discount': line.discount,
954 'product_id': line.product_id.id or False,
955 'invoice_line_tax_id': [(6, 0, [x.id for x in line.tax_id])],
957 'account_analytic_id': line.order_id.project_id and line.order_id.project_id.id or False,
959 cr.execute('insert into sale_order_line_invoice_rel (order_line_id,invoice_id) values (%s,%s)', (line.id, inv_id))
960 self.write(cr, uid, [line.id], {'invoiced': True})
961 sales[line.order_id.id] = True
962 create_ids.append(inv_id)
963 # Trigger workflow events
964 wf_service = netsvc.LocalService("workflow")
965 for sid in sales.keys():
966 wf_service.trg_write(uid, 'sale.order', sid, cr)
969 def button_cancel(self, cr, uid, ids, context=None):
970 for line in self.browse(cr, uid, ids, context=context):
972 raise osv.except_osv(_('Invalid action !'), _('You cannot cancel a sales order line that has already been invoiced !'))
973 for move_line in line.move_ids:
974 if move_line.state != 'cancel':
975 raise osv.except_osv(
976 _('Could not cancel sales order line!'),
977 _('You must first cancel stock moves attached to this sales order line.'))
978 return self.write(cr, uid, ids, {'state': 'cancel'})
980 def button_confirm(self, cr, uid, ids, context=None):
981 return self.write(cr, uid, ids, {'state': 'confirmed'})
983 def button_done(self, cr, uid, ids, context=None):
984 wf_service = netsvc.LocalService("workflow")
985 res = self.write(cr, uid, ids, {'state': 'done'})
986 for line in self.browse(cr, uid, ids, context=context):
987 wf_service.trg_write(uid, 'sale.order', line.order_id.id, cr)
990 def uos_change(self, cr, uid, ids, product_uos, product_uos_qty=0, product_id=None):
991 product_obj = self.pool.get('product.product')
993 return {'value': {'product_uom': product_uos,
994 'product_uom_qty': product_uos_qty}, 'domain': {}}
996 product = product_obj.browse(cr, uid, product_id)
998 'product_uom': product.uom_id.id,
1000 # FIXME must depend on uos/uom of the product and not only of the coeff.
1003 'product_uom_qty': product_uos_qty / product.uos_coeff,
1004 'th_weight': product_uos_qty / product.uos_coeff * product.weight
1006 except ZeroDivisionError:
1008 return {'value': value}
1010 def copy_data(self, cr, uid, id, default=None, context=None):
1013 default.update({'state': 'draft', 'move_ids': [], 'invoiced': False, 'invoice_lines': []})
1014 return super(sale_order_line, self).copy_data(cr, uid, id, default, context=context)
1016 def product_id_change(self, cr, uid, ids, pricelist, product, qty=0,
1017 uom=False, qty_uos=0, uos=False, name='', partner_id=False,
1018 lang=False, update_tax=True, date_order=False, packaging=False, fiscal_position=False, flag=False):
1020 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.'))
1022 product_uom_obj = self.pool.get('product.uom')
1023 partner_obj = self.pool.get('res.partner')
1024 product_obj = self.pool.get('product.product')
1026 lang = partner_obj.browse(cr, uid, partner_id).lang
1027 context = {'lang': lang, 'partner_id': partner_id}
1030 return {'value': {'th_weight': 0, 'product_packaging': False,
1031 'product_uos_qty': qty}, 'domain': {'product_uom': [],
1034 date_order = time.strftime('%Y-%m-%d')
1037 product_obj = product_obj.browse(cr, uid, product, context=context)
1038 if not packaging and product_obj.packaging:
1039 packaging = product_obj.packaging[0].id
1040 result['product_packaging'] = packaging
1043 default_uom = product_obj.uom_id and product_obj.uom_id.id
1044 pack = self.pool.get('product.packaging').browse(cr, uid, packaging, context=context)
1045 q = product_uom_obj._compute_qty(cr, uid, uom, pack.qty, default_uom)
1046 # qty = qty - qty % q + q
1047 if qty and (q and not (qty % q) == 0):
1048 ean = pack.ean or _('(n/a)')
1051 warn_msg = _("You selected a quantity of %d Units.\n"
1052 "But it's not compatible with the selected packaging.\n"
1053 "Here is a proposition of quantities according to the packaging:\n\n"
1054 "EAN: %s Quantity: %s Type of ul: %s") % \
1055 (qty, ean, qty_pack, type_ul.name)
1057 'title': _('Picking Information !'),
1060 result['product_uom_qty'] = qty
1064 uom2 = product_uom_obj.browse(cr, uid, uom)
1065 if product_obj.uom_id.category_id.id != uom2.category_id.id:
1068 if product_obj.uos_id:
1069 uos2 = product_uom_obj.browse(cr, uid, uos)
1070 if product_obj.uos_id.category_id.id != uos2.category_id.id:
1074 if product_obj.description_sale:
1075 result['notes'] = product_obj.description_sale
1076 fpos = fiscal_position and self.pool.get('account.fiscal.position').browse(cr, uid, fiscal_position) or False
1077 if update_tax: #The quantity only have changed
1078 result['delay'] = (product_obj.sale_delay or 0.0)
1079 result['tax_id'] = self.pool.get('account.fiscal.position').map_tax(cr, uid, fpos, product_obj.taxes_id)
1080 result.update({'type': product_obj.procure_method})
1083 result['name'] = self.pool.get('product.product').name_get(cr, uid, [product_obj.id], context=context)[0][1]
1085 if (not uom) and (not uos):
1086 result['product_uom'] = product_obj.uom_id.id
1087 if product_obj.uos_id:
1088 result['product_uos'] = product_obj.uos_id.id
1089 result['product_uos_qty'] = qty * product_obj.uos_coeff
1090 uos_category_id = product_obj.uos_id.category_id.id
1092 result['product_uos'] = False
1093 result['product_uos_qty'] = qty
1094 uos_category_id = False
1095 result['th_weight'] = qty * product_obj.weight
1096 domain = {'product_uom':
1097 [('category_id', '=', product_obj.uom_id.category_id.id)],
1099 [('category_id', '=', uos_category_id)]}
1101 elif uos and not uom: # only happens if uom is False
1102 result['product_uom'] = product_obj.uom_id and product_obj.uom_id.id
1103 result['product_uom_qty'] = qty_uos / product_obj.uos_coeff
1104 result['th_weight'] = result['product_uom_qty'] * product_obj.weight
1105 elif uom: # whether uos is set or not
1106 default_uom = product_obj.uom_id and product_obj.uom_id.id
1107 q = product_uom_obj._compute_qty(cr, uid, uom, qty, default_uom)
1108 if product_obj.uos_id:
1109 result['product_uos'] = product_obj.uos_id.id
1110 result['product_uos_qty'] = qty * product_obj.uos_coeff
1112 result['product_uos'] = False
1113 result['product_uos_qty'] = qty
1114 result['th_weight'] = q * product_obj.weight # Round the quantity up
1117 uom2 = product_obj.uom_id
1118 if (product_obj.type=='product') and (product_obj.virtual_available * uom2.factor < qty * product_obj.uom_id.factor) \
1119 and (product_obj.procure_method=='make_to_stock'):
1121 'title': _('Not enough stock !'),
1122 'message': _('You plan to sell %.2f %s but you only have %.2f %s available !\nThe real stock is %.2f %s. (without reservations)') %
1123 (qty, uom2 and uom2.name or product_obj.uom_id.name,
1124 max(0,product_obj.virtual_available), product_obj.uom_id.name,
1125 max(0,product_obj.qty_available), product_obj.uom_id.name)
1130 'title': 'No Pricelist !',
1132 'You have to select a pricelist or a customer in the sales form !\n'
1133 'Please set one before choosing a product.'
1136 price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist],
1137 product, qty or 1.0, partner_id, {
1143 'title': 'No valid pricelist line found !',
1145 "Couldn't find a pricelist line matching this product and quantity.\n"
1146 "You have to change either the product, the quantity or the pricelist."
1149 result.update({'price_unit': price})
1150 return {'value': result, 'domain': domain, 'warning': warning}
1152 def product_uom_change(self, cursor, user, ids, pricelist, product, qty=0,
1153 uom=False, qty_uos=0, uos=False, name='', partner_id=False,
1154 lang=False, update_tax=True, date_order=False):
1155 res = self.product_id_change(cursor, user, ids, pricelist, product,
1156 qty=qty, uom=uom, qty_uos=qty_uos, uos=uos, name=name,
1157 partner_id=partner_id, lang=lang, update_tax=update_tax,
1158 date_order=date_order)
1159 if 'product_uom' in res['value']:
1160 del res['value']['product_uom']
1162 res['value']['price_unit'] = 0.0
1165 def unlink(self, cr, uid, ids, context=None):
1168 """Allows to delete sales order lines in draft,cancel states"""
1169 for rec in self.browse(cr, uid, ids, context=context):
1170 if rec.state not in ['draft', 'cancel']:
1171 raise osv.except_osv(_('Invalid action !'), _('Cannot delete a sales order line which is %s !') %(rec.state,))
1172 return super(sale_order_line, self).unlink(cr, uid, ids, context=context)
1176 class sale_config_picking_policy(osv.osv_memory):
1177 _name = 'sale.config.picking_policy'
1178 _inherit = 'res.config'
1181 'name': fields.char('Name', size=64),
1182 'picking_policy': fields.selection([
1183 ('direct', 'Direct Delivery'),
1184 ('one', 'All at Once')
1185 ], '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.."),
1186 'order_policy': fields.selection([
1187 ('manual', 'Invoice Based on Sales Orders'),
1188 ('picking', 'Invoice Based on Deliveries'),
1189 ], 'Shipping Default Policy', required=True,
1190 help="You can generate invoices based on sales orders or based on shippings."),
1191 'step': fields.selection([
1192 ('one', 'Delivery Order Only'),
1193 ('two', 'Picking List & Delivery Order')
1194 ], 'Steps To Deliver a Sales Order', required=True,
1195 help="By default, OpenERP is able to manage complex routing and paths "\
1196 "of products in your warehouse and partner locations. This will configure "\
1197 "the most common and simple methods to deliver products to the customer "\
1198 "in one or two operations by the worker.")
1201 'picking_policy': 'direct',
1202 'order_policy': 'manual',
1206 def execute(self, cr, uid, ids, context=None):
1207 for o in self.browse(cr, uid, ids, context=context):
1208 ir_values_obj = self.pool.get('ir.values')
1209 ir_values_obj.set(cr, uid, 'default', False, 'picking_policy', ['sale.order'], o.picking_policy)
1210 ir_values_obj.set(cr, uid, 'default', False, 'order_policy', ['sale.order'], o.order_policy)
1212 md = self.pool.get('ir.model.data')
1213 location_id = md.get_object_reference(cr, uid, 'stock', 'stock_location_output')
1214 location_id = location_id and location_id[1] or False
1215 self.pool.get('stock.location').write(cr, uid, [location_id], {'chained_auto_packing': 'manual'})
1217 sale_config_picking_policy()
1219 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: